@opendaw/studio-core 0.0.48 → 0.0.50

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.
Files changed (37) hide show
  1. package/dist/AudioOfflineRenderer.d.ts +2 -2
  2. package/dist/AudioOfflineRenderer.d.ts.map +1 -1
  3. package/dist/AudioOfflineRenderer.js +5 -68
  4. package/dist/Engine.d.ts +2 -2
  5. package/dist/Engine.d.ts.map +1 -1
  6. package/dist/ffmpeg/FFmpegConverter.d.ts +6 -0
  7. package/dist/ffmpeg/FFmpegConverter.d.ts.map +1 -0
  8. package/dist/ffmpeg/FFmpegConverter.js +1 -0
  9. package/dist/ffmpeg/FFmpegWorker.d.ts +18 -0
  10. package/dist/ffmpeg/FFmpegWorker.d.ts.map +1 -0
  11. package/dist/ffmpeg/FFmpegWorker.js +115 -0
  12. package/dist/ffmpeg/flac.d.ts +12 -0
  13. package/dist/ffmpeg/flac.d.ts.map +1 -0
  14. package/dist/ffmpeg/flac.js +32 -0
  15. package/dist/ffmpeg/index.d.ts +3 -0
  16. package/dist/ffmpeg/index.d.ts.map +1 -0
  17. package/dist/ffmpeg/index.js +2 -0
  18. package/dist/ffmpeg/mp3.d.ts +13 -0
  19. package/dist/ffmpeg/mp3.d.ts.map +1 -0
  20. package/dist/ffmpeg/mp3.js +37 -0
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -0
  24. package/dist/processors.js +14 -14
  25. package/dist/processors.js.map +4 -4
  26. package/dist/project/ProjectApi.d.ts +1 -9
  27. package/dist/project/ProjectApi.d.ts.map +1 -1
  28. package/dist/project/ProjectApi.js +3 -67
  29. package/dist/project/ProjectUtils.d.ts +12 -0
  30. package/dist/project/ProjectUtils.d.ts.map +1 -0
  31. package/dist/project/ProjectUtils.js +145 -0
  32. package/dist/project/index.d.ts +1 -0
  33. package/dist/project/index.d.ts.map +1 -1
  34. package/dist/project/index.js +1 -0
  35. package/dist/workers-main.js +2 -2
  36. package/dist/workers-main.js.map +2 -2
  37. package/package.json +21 -15
@@ -1,7 +1,7 @@
1
1
  import { int, Option } from "@opendaw/lib-std";
2
2
  import { ExportStemsConfiguration } from "@opendaw/studio-adapters";
3
- import { Project, ProjectMeta } from "./project";
3
+ import { Project } from "./project";
4
4
  export declare namespace AudioOfflineRenderer {
5
- const start: (source: Project, meta: ProjectMeta, optExportConfiguration: Option<ExportStemsConfiguration>, sampleRate?: int) => Promise<void>;
5
+ const start: (source: Project, optExportConfiguration: Option<ExportStemsConfiguration>, sampleRate?: int) => Promise<AudioBuffer>;
6
6
  }
7
7
  //# sourceMappingURL=AudioOfflineRenderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AudioOfflineRenderer.d.ts","sourceRoot":"","sources":["../src/AudioOfflineRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,GAAG,EAAE,MAAM,EAAmC,MAAM,kBAAkB,CAAA;AAI9G,OAAO,EAAC,wBAAwB,EAAC,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAAC,OAAO,EAAE,WAAW,EAAC,MAAM,WAAW,CAAA;AAI9C,yBAAiB,oBAAoB,CAAC;IAC3B,MAAM,KAAK,GAAU,QAAQ,OAAO,EACf,MAAM,WAAW,EACjB,wBAAwB,MAAM,CAAC,wBAAwB,CAAC,EACxD,aAAY,GAAY,KAAG,OAAO,CAAC,IAAI,CAgClE,CAAA;CAwDJ"}
1
+ {"version":3,"file":"AudioOfflineRenderer.d.ts","sourceRoot":"","sources":["../src/AudioOfflineRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,GAAG,EAAE,MAAM,EAAmC,MAAM,kBAAkB,CAAA;AAItG,OAAO,EAAC,wBAAwB,EAAC,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAGjC,yBAAiB,oBAAoB,CAAC;IAC3B,MAAM,KAAK,GAAU,QAAQ,OAAO,EACf,wBAAwB,MAAM,CAAC,wBAAwB,CAAC,EACxD,aAAY,GAAY,KAAG,OAAO,CAAC,WAAW,CA4BzE,CAAA;CACJ"}
@@ -1,13 +1,12 @@
1
- import { DefaultObservableValue, Errors, panic, RuntimeNotifier, TimeSpan } from "@opendaw/lib-std";
1
+ import { DefaultObservableValue, panic, RuntimeNotifier, TimeSpan } from "@opendaw/lib-std";
2
2
  import { PPQN } from "@opendaw/lib-dsp";
3
- import { AnimationFrame, Files } from "@opendaw/lib-dom";
4
- import { Promises, Wait } from "@opendaw/lib-runtime";
3
+ import { AnimationFrame } from "@opendaw/lib-dom";
4
+ import { Wait } from "@opendaw/lib-runtime";
5
5
  import { ExportStemsConfiguration } from "@opendaw/studio-adapters";
6
- import { WavFile } from "./WavFile";
7
6
  import { AudioWorklets } from "./AudioWorklets";
8
7
  export var AudioOfflineRenderer;
9
8
  (function (AudioOfflineRenderer) {
10
- AudioOfflineRenderer.start = async (source, meta, optExportConfiguration, sampleRate = 48_000) => {
9
+ AudioOfflineRenderer.start = async (source, optExportConfiguration, sampleRate = 48_000) => {
11
10
  const numStems = ExportStemsConfiguration.countStems(optExportConfiguration);
12
11
  if (numStems === 0) {
13
12
  return panic("Nothing to export");
@@ -38,68 +37,6 @@ export var AudioOfflineRenderer;
38
37
  terminable.terminate();
39
38
  dialog.terminate();
40
39
  project.terminate();
41
- if (optExportConfiguration.isEmpty()) {
42
- await saveWavFile(buffer, meta);
43
- }
44
- else {
45
- await saveZipFile(buffer, meta, Object.values(optExportConfiguration.unwrap()).map(({ fileName }) => fileName));
46
- }
47
- };
48
- const saveWavFile = async (buffer, meta) => {
49
- const approved = await RuntimeNotifier.approve({
50
- headline: "Save Wav-File",
51
- message: "",
52
- approveText: "Save"
53
- });
54
- if (!approved) {
55
- return;
56
- }
57
- const wavFile = WavFile.encodeFloats(buffer);
58
- const suggestedName = `${meta.name}.wav`;
59
- const saveResult = await Promises.tryCatch(Files.save(wavFile, { suggestedName }));
60
- if (saveResult.status === "rejected" && !Errors.isAbort(saveResult.error)) {
61
- panic(String(saveResult.error));
62
- }
63
- };
64
- const saveZipFile = async (buffer, meta, trackNames) => {
65
- const { default: JSZip } = await import("jszip");
66
- const dialog = RuntimeNotifier.progress({ headline: "Creating Zip File..." });
67
- const numStems = buffer.numberOfChannels >> 1;
68
- const zip = new JSZip();
69
- for (let stemIndex = 0; stemIndex < numStems; stemIndex++) {
70
- const l = buffer.getChannelData(stemIndex * 2);
71
- const r = buffer.getChannelData(stemIndex * 2 + 1);
72
- const file = WavFile.encodeFloats({
73
- channels: [l, r],
74
- sampleRate: buffer.sampleRate,
75
- numFrames: buffer.length
76
- });
77
- zip.file(`${trackNames[stemIndex]}.wav`, file, { binary: true });
78
- }
79
- const { status, value: arrayBuffer, error } = await Promises.tryCatch(zip.generateAsync({
80
- type: "arraybuffer",
81
- compression: "DEFLATE",
82
- compressionOptions: { level: 6 }
83
- }));
84
- dialog.terminate();
85
- if (status === "rejected") {
86
- await RuntimeNotifier.info({
87
- headline: "Error",
88
- message: `Could not create zip file: ${String(error)}`
89
- });
90
- return;
91
- }
92
- const approved = await RuntimeNotifier.approve({
93
- headline: "Save Zip",
94
- message: `Size: ${arrayBuffer.byteLength >> 20}M`,
95
- approveText: "Save"
96
- });
97
- if (!approved) {
98
- return;
99
- }
100
- const saveResult = await Promises.tryCatch(Files.save(arrayBuffer, { suggestedName: `${meta.name}.zip` }));
101
- if (saveResult.status === "rejected" && !Errors.isAbort(saveResult.error)) {
102
- panic(String(saveResult.error));
103
- }
40
+ return buffer;
104
41
  };
105
42
  })(AudioOfflineRenderer || (AudioOfflineRenderer = {}));
package/dist/Engine.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { ppqn } from "@opendaw/lib-dsp";
2
1
  import { int, MutableObservableValue, Nullable, ObservableValue, Observer, Subscription, Terminable, UUID } from "@opendaw/lib-std";
2
+ import { ppqn } from "@opendaw/lib-dsp";
3
3
  import { ClipNotification, NoteSignal } from "@opendaw/studio-adapters";
4
- import { Project } from "./project/Project";
4
+ import { Project } from "./project";
5
5
  export interface Engine extends Terminable {
6
6
  play(): void;
7
7
  stop(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../src/Engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAA;AACrC,OAAO,EACH,GAAG,EACH,sBAAsB,EACtB,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,IAAI,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAC,gBAAgB,EAAE,UAAU,EAAC,MAAM,0BAA0B,CAAA;AACrE,OAAO,EAAC,OAAO,EAAC,MAAM,mBAAmB,CAAA;AAEzC,MAAM,WAAW,MAAO,SAAQ,UAAU;IACtC,IAAI,IAAI,IAAI,CAAA;IACZ,IAAI,IAAI,IAAI,CAAA;IACZ,WAAW,CAAC,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAA;IACjC,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IAC7C,aAAa,IAAI,IAAI,CAAA;IACrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACxB,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,IAAI,IAAI,IAAI,CAAA;IACZ,KAAK,IAAI,IAAI,CAAA;IACb,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAA;IACpC,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,YAAY,CAAA;IAC5D,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;IACxC,gBAAgB,CAAC,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAA;IAC1D,gBAAgB,CAAC,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAA;IAC3D,yBAAyB,CAAC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GAAG,YAAY,CAAA;IAE7E,IAAI,QAAQ,IAAI,eAAe,CAAC,IAAI,CAAC,CAAA;IACrC,IAAI,SAAS,IAAI,eAAe,CAAC,OAAO,CAAC,CAAA;IACzC,IAAI,WAAW,IAAI,eAAe,CAAC,OAAO,CAAC,CAAA;IAC3C,IAAI,YAAY,IAAI,eAAe,CAAC,OAAO,CAAC,CAAA;IAC5C,IAAI,gBAAgB,IAAI,eAAe,CAAC,OAAO,CAAC,CAAA;IAChD,IAAI,iBAAiB,IAAI,eAAe,CAAC,IAAI,CAAC,CAAA;IAC9C,IAAI,wBAAwB,IAAI,sBAAsB,CAAC,OAAO,CAAC,CAAA;IAC/D,IAAI,gBAAgB,IAAI,eAAe,CAAC,GAAG,CAAC,CAAA;IAC5C,IAAI,qBAAqB,IAAI,eAAe,CAAC,MAAM,CAAC,CAAA;IACpD,IAAI,WAAW,IAAI,eAAe,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;IAC/D,IAAI,OAAO,IAAI,OAAO,CAAA;CACzB"}
1
+ {"version":3,"file":"Engine.d.ts","sourceRoot":"","sources":["../src/Engine.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,GAAG,EACH,sBAAsB,EACtB,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,IAAI,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAA;AACrC,OAAO,EAAC,gBAAgB,EAAE,UAAU,EAAC,MAAM,0BAA0B,CAAA;AACrE,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AAEjC,MAAM,WAAW,MAAO,SAAQ,UAAU;IACtC,IAAI,IAAI,IAAI,CAAA;IACZ,IAAI,IAAI,IAAI,CAAA;IACZ,WAAW,CAAC,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAA;IACjC,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IAC7C,aAAa,IAAI,IAAI,CAAA;IACrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACxB,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;IACxC,IAAI,IAAI,IAAI,CAAA;IACZ,KAAK,IAAI,IAAI,CAAA;IACb,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAA;IACpC,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,YAAY,CAAA;IAC5D,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;IACxC,gBAAgB,CAAC,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAA;IAC1D,gBAAgB,CAAC,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAA;IAC3D,yBAAyB,CAAC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,GAAG,YAAY,CAAA;IAE7E,IAAI,QAAQ,IAAI,eAAe,CAAC,IAAI,CAAC,CAAA;IACrC,IAAI,SAAS,IAAI,eAAe,CAAC,OAAO,CAAC,CAAA;IACzC,IAAI,WAAW,IAAI,eAAe,CAAC,OAAO,CAAC,CAAA;IAC3C,IAAI,YAAY,IAAI,eAAe,CAAC,OAAO,CAAC,CAAA;IAC5C,IAAI,gBAAgB,IAAI,eAAe,CAAC,OAAO,CAAC,CAAA;IAChD,IAAI,iBAAiB,IAAI,eAAe,CAAC,IAAI,CAAC,CAAA;IAC9C,IAAI,wBAAwB,IAAI,sBAAsB,CAAC,OAAO,CAAC,CAAA;IAC/D,IAAI,gBAAgB,IAAI,eAAe,CAAC,GAAG,CAAC,CAAA;IAC5C,IAAI,qBAAqB,IAAI,eAAe,CAAC,MAAM,CAAC,CAAA;IACpD,IAAI,WAAW,IAAI,eAAe,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;IAC/D,IAAI,OAAO,IAAI,OAAO,CAAA;CACzB"}
@@ -0,0 +1,6 @@
1
+ import { AcceptedSource } from "./FFmpegWorker";
2
+ import { Progress } from "@opendaw/lib-std";
3
+ export interface FFmpegConverter<OPTIONS> {
4
+ convert(source: AcceptedSource, progress: Progress.Handler, options?: OPTIONS): Promise<ArrayBuffer>;
5
+ }
6
+ //# sourceMappingURL=FFmpegConverter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FFmpegConverter.d.ts","sourceRoot":"","sources":["../../src/ffmpeg/FFmpegConverter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,cAAc,EAAC,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAC,QAAQ,EAAC,MAAM,kBAAkB,CAAA;AAEzC,MAAM,WAAW,eAAe,CAAC,OAAO;IACpC,OAAO,CAAC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,QAAQ,CAAC,OAAO,EAC1B,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;CACnD"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import { Notifier, Progress, unitValue } from "@opendaw/lib-std";
2
+ import type { FFmpeg } from "@ffmpeg/ffmpeg";
3
+ import { Mp3Converter } from "./mp3";
4
+ import { FlacConverter } from "./flac";
5
+ export type AcceptedSource = File | Blob;
6
+ export declare class FFmpegWorker {
7
+ #private;
8
+ static load(progress?: Progress.Handler): Promise<FFmpegWorker>;
9
+ constructor(ffmpeg: FFmpeg);
10
+ get ffmpeg(): FFmpeg;
11
+ get loaded(): boolean;
12
+ get progressNotifier(): Notifier<unitValue>;
13
+ mp3Converter(): Mp3Converter;
14
+ flacConverter(): FlacConverter;
15
+ fetchFileData(source: string): Promise<Uint8Array>;
16
+ cleanupFiles(files: string[]): Promise<void>;
17
+ }
18
+ //# sourceMappingURL=FFmpegWorker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FFmpegWorker.d.ts","sourceRoot":"","sources":["../../src/ffmpeg/FFmpegWorker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,QAAQ,EAAY,QAAQ,EAAE,SAAS,EAAC,MAAM,kBAAkB,CAAA;AACzF,OAAO,KAAK,EAAC,MAAM,EAAW,MAAM,gBAAgB,CAAA;AACpD,OAAO,EAAC,YAAY,EAAC,MAAM,OAAO,CAAA;AAClC,OAAO,EAAC,aAAa,EAAC,MAAM,QAAQ,CAAA;AAEpC,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,CAAA;AAExC,qBAAa,YAAY;;WACR,IAAI,CAAC,QAAQ,GAAE,QAAQ,CAAC,OAAwB,GAAG,OAAO,CAAC,YAAY,CAAC;gBAOzE,MAAM,EAAE,MAAM;IAO1B,IAAI,MAAM,IAAI,MAAM,CAAsB;IAC1C,IAAI,MAAM,IAAI,OAAO,CAA6B;IAClD,IAAI,gBAAgB,IAAI,QAAQ,CAAC,SAAS,CAAC,CAAgC;IAG3E,YAAY,IAAI,YAAY;IAG5B,aAAa,IAAI,aAAa;IAExB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAQlD,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAGrD"}
@@ -0,0 +1,115 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { EmptyExec, Lazy, Notifier, Progress } from "@opendaw/lib-std";
11
+ import { Mp3Converter } from "./mp3";
12
+ import { FlacConverter } from "./flac";
13
+ export class FFmpegWorker {
14
+ static async load(progress = Progress.Empty) {
15
+ return Loader.loadOrAttach(progress);
16
+ }
17
+ #ffmpeg;
18
+ #progressNotifier;
19
+ constructor(ffmpeg) {
20
+ this.#ffmpeg = ffmpeg;
21
+ this.#progressNotifier = new Notifier();
22
+ this.#ffmpeg.on("log", ({ message }) => console.debug("[FFmpeg]", message));
23
+ this.#ffmpeg.on("progress", event => this.#progressNotifier.notify(event.progress));
24
+ }
25
+ get ffmpeg() { return this.#ffmpeg; }
26
+ get loaded() { return this.#ffmpeg.loaded; }
27
+ get progressNotifier() { return this.#progressNotifier; }
28
+ mp3Converter() { return new Mp3Converter(this); }
29
+ flacConverter() { return new FlacConverter(this); }
30
+ async fetchFileData(source) {
31
+ const response = await fetch(source);
32
+ if (!response.ok) {
33
+ throw new Error(`Failed to fetch file: ${response.statusText}`);
34
+ }
35
+ return new Uint8Array(await response.arrayBuffer());
36
+ }
37
+ async cleanupFiles(files) {
38
+ return Promise.all(files.map(file => this.#ffmpeg.deleteFile(file).catch())).then(EmptyExec);
39
+ }
40
+ }
41
+ __decorate([
42
+ Lazy,
43
+ __metadata("design:type", Function),
44
+ __metadata("design:paramtypes", []),
45
+ __metadata("design:returntype", Mp3Converter)
46
+ ], FFmpegWorker.prototype, "mp3Converter", null);
47
+ __decorate([
48
+ Lazy,
49
+ __metadata("design:type", Function),
50
+ __metadata("design:paramtypes", []),
51
+ __metadata("design:returntype", FlacConverter)
52
+ ], FFmpegWorker.prototype, "flacConverter", null);
53
+ class Loader {
54
+ static async loadOrAttach(progress) {
55
+ if (this.#loader === null) {
56
+ this.#loader = new Loader();
57
+ }
58
+ const subscription = this.#loader.#progressNotifier.subscribe(progress);
59
+ return this.#loader.load().finally(() => subscription.terminate());
60
+ }
61
+ static #loader = null;
62
+ #progressNotifier = new Notifier();
63
+ async load() {
64
+ const { FFmpeg } = await import("@ffmpeg/ffmpeg");
65
+ const ffmpeg = new FFmpeg();
66
+ ffmpeg.on("log", ({ type, message }) => {
67
+ console.debug(`[FFmpeg ${type}]`, message);
68
+ });
69
+ ffmpeg.on("progress", event => {
70
+ this.#progressNotifier.notify(event.progress);
71
+ });
72
+ const baseURL = "https://package.opendaw.studio"; // mirror of https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm
73
+ console.debug("[FFmpeg] Downloading core files...");
74
+ const downloadWithProgress = async (url) => {
75
+ const response = await fetch(url);
76
+ if (!response.ok)
77
+ throw new Error(`Failed to fetch ${url}`);
78
+ const contentLength = response.headers.get("content-length");
79
+ const total = contentLength ? parseInt(contentLength, 10) : 0;
80
+ if (!response.body || total === 0) {
81
+ return response.arrayBuffer();
82
+ }
83
+ const reader = response.body.getReader();
84
+ const chunks = [];
85
+ let received = 0;
86
+ while (true) {
87
+ const { done, value } = await reader.read();
88
+ if (done)
89
+ break;
90
+ chunks.push(value);
91
+ received += value.length;
92
+ this.#progressNotifier.notify(received / total);
93
+ }
94
+ const result = new Uint8Array(received);
95
+ let position = 0;
96
+ for (const chunk of chunks) {
97
+ result.set(chunk, position);
98
+ position += chunk.length;
99
+ }
100
+ console.debug("position", position);
101
+ return result.buffer;
102
+ };
103
+ const coreData = await downloadWithProgress(`${baseURL}/ffmpeg-core.js`);
104
+ const wasmData = await downloadWithProgress(`${baseURL}/ffmpeg-core.wasm`);
105
+ console.debug("[FFmpeg] Creating blob URLs...");
106
+ const coreBlob = new Blob([coreData], { type: "text/javascript" });
107
+ const wasmBlob = new Blob([wasmData], { type: "application/wasm" });
108
+ const coreURL = URL.createObjectURL(coreBlob);
109
+ const wasmURL = URL.createObjectURL(wasmBlob);
110
+ console.debug("[FFmpeg] Initializing...");
111
+ await ffmpeg.load({ coreURL, wasmURL });
112
+ console.debug("[FFmpeg] Ready");
113
+ return new FFmpegWorker(ffmpeg);
114
+ }
115
+ }
@@ -0,0 +1,12 @@
1
+ import { FFmpegConverter } from "./FFmpegConverter";
2
+ import { int, Progress } from "@opendaw/lib-std";
3
+ import { AcceptedSource, FFmpegWorker } from "./FFmpegWorker";
4
+ export type FlacOptions = {
5
+ compression: int;
6
+ };
7
+ export declare class FlacConverter implements FFmpegConverter<FlacOptions> {
8
+ #private;
9
+ constructor(worker: FFmpegWorker);
10
+ convert(source: AcceptedSource, progress: Progress.Handler, options?: FlacOptions): Promise<ArrayBuffer>;
11
+ }
12
+ //# sourceMappingURL=flac.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flac.d.ts","sourceRoot":"","sources":["../../src/ffmpeg/flac.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,GAAG,EAAE,QAAQ,EAAC,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAC,cAAc,EAAE,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAG3D,MAAM,MAAM,WAAW,GAAG;IAAE,WAAW,EAAE,GAAG,CAAA;CAAE,CAAA;AAE9C,qBAAa,aAAc,YAAW,eAAe,CAAC,WAAW,CAAC;;gBAGlD,MAAM,EAAE,YAAY;IAC1B,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;CA0BjH"}
@@ -0,0 +1,32 @@
1
+ export class FlacConverter {
2
+ #worker;
3
+ constructor(worker) { this.#worker = worker; }
4
+ async convert(source, progress, options) {
5
+ const subscription = this.#worker.progressNotifier.subscribe(progress);
6
+ try {
7
+ let inputData;
8
+ if (source instanceof File || source instanceof Blob) {
9
+ inputData = new Uint8Array(await source.arrayBuffer());
10
+ }
11
+ else {
12
+ inputData = await this.#worker.fetchFileData(source);
13
+ }
14
+ await this.#worker.ffmpeg.writeFile("input.wav", inputData);
15
+ await this.#worker.ffmpeg.exec([
16
+ "-y",
17
+ "-i", "input.wav",
18
+ "-compression_level", String(options?.compression ?? 8),
19
+ "output.flac"
20
+ ]);
21
+ const outputData = await this.#worker.ffmpeg.readFile("output.flac");
22
+ if (typeof outputData === "string") {
23
+ return Promise.reject(outputData);
24
+ }
25
+ return new Blob([new Uint8Array(outputData)], { type: "audio/flac" }).arrayBuffer();
26
+ }
27
+ finally {
28
+ subscription.terminate();
29
+ await this.#worker.cleanupFiles(["input.wav", "output.flac"]);
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./FFmpegWorker";
2
+ export * from "./FFmpegConverter";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ffmpeg/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,mBAAmB,CAAA"}
@@ -0,0 +1,2 @@
1
+ export * from "./FFmpegWorker";
2
+ export * from "./FFmpegConverter";
@@ -0,0 +1,13 @@
1
+ import { Progress } from "@opendaw/lib-std";
2
+ import { AcceptedSource, FFmpegWorker } from "./FFmpegWorker";
3
+ import { FFmpegConverter } from "./FFmpegConverter";
4
+ export type Mp3Options = {
5
+ bitrate?: string;
6
+ quality?: number;
7
+ };
8
+ export declare class Mp3Converter implements FFmpegConverter<Mp3Options> {
9
+ #private;
10
+ constructor(worker: FFmpegWorker);
11
+ convert(source: AcceptedSource, progress: Progress.Handler, options?: Mp3Options): Promise<ArrayBuffer>;
12
+ }
13
+ //# sourceMappingURL=mp3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mp3.d.ts","sourceRoot":"","sources":["../../src/ffmpeg/mp3.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAC,cAAc,EAAE,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3D,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AAEjD,MAAM,MAAM,UAAU,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/D,qBAAa,YAAa,YAAW,eAAe,CAAC,UAAU,CAAC;;gBAGhD,MAAM,EAAE,YAAY;IAE1B,OAAO,CAAC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,QAAQ,CAAC,OAAO,EAC1B,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,WAAW,CAAC;CA8BhE"}
@@ -0,0 +1,37 @@
1
+ export class Mp3Converter {
2
+ #worker;
3
+ constructor(worker) { this.#worker = worker; }
4
+ async convert(source, progress, options = {}) {
5
+ const subscription = this.#worker.progressNotifier.subscribe(progress);
6
+ try {
7
+ let inputData;
8
+ if (source instanceof File || source instanceof Blob) {
9
+ inputData = new Uint8Array(await source.arrayBuffer());
10
+ }
11
+ else {
12
+ inputData = await this.#worker.fetchFileData(source);
13
+ }
14
+ await this.#worker.ffmpeg.writeFile("input.wav", inputData);
15
+ const args = ["-y", "-i", "input.wav"];
16
+ if (options.quality !== undefined) {
17
+ // VBR mode: quality 0 (best) to 9 (worst), 2 is recommended
18
+ args.push("-q:a", (options.quality ?? 2).toString());
19
+ }
20
+ else {
21
+ // CBR mode: default to 320k (high quality)
22
+ args.push("-b:a", options.bitrate ?? "320k");
23
+ }
24
+ args.push("output.mp3");
25
+ await this.#worker.ffmpeg.exec(args);
26
+ const outputData = await this.#worker.ffmpeg.readFile("output.mp3");
27
+ if (typeof outputData === "string") {
28
+ return Promise.reject(outputData);
29
+ }
30
+ return new Blob([new Uint8Array(outputData)], { type: "audio/mpeg" }).arrayBuffer();
31
+ }
32
+ finally {
33
+ subscription.terminate();
34
+ await this.#worker.cleanupFiles(["input.wav", "output.mp3"]);
35
+ }
36
+ }
37
+ }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./capture";
2
2
  export * from "./cloud";
3
3
  export * from "./dawproject";
4
+ export * from "./ffmpeg";
4
5
  export * from "./midi";
5
6
  export * from "./project";
6
7
  export * from "./samples";
@@ -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,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;AACvB,cAAc,SAAS,CAAA;AAEvB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,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,yBAAyB,CAAA;AACvC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,SAAS,CAAA;AACvB,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,iBAAiB,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,cAAc,UAAU,CAAA;AACxB,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;AACvB,cAAc,SAAS,CAAA;AAEvB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,wBAAwB,CAAA;AACtC,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,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,yBAAyB,CAAA;AACvC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,SAAS,CAAA;AACvB,cAAc,eAAe,CAAA;AAC7B,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,iBAAiB,CAAA"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./capture";
2
2
  export * from "./cloud";
3
3
  export * from "./dawproject";
4
+ export * from "./ffmpeg";
4
5
  export * from "./midi";
5
6
  export * from "./project";
6
7
  export * from "./samples";