@opendaw/studio-core 0.0.17 → 0.0.19
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/Project.d.ts +3 -1
- package/dist/Project.d.ts.map +1 -1
- package/dist/Project.js +17 -2
- package/dist/dawproject/DawProjectExporter.d.ts +8 -0
- package/dist/dawproject/DawProjectExporter.d.ts.map +1 -0
- package/dist/dawproject/DawProjectExporter.js +29 -0
- package/dist/dawproject/DawProjectExporter.test.d.ts +2 -0
- package/dist/dawproject/DawProjectExporter.test.d.ts.map +1 -0
- package/dist/dawproject/DawProjectExporter.test.js +16 -0
- package/dist/dawproject/DawProjectIO.d.ts +20 -0
- package/dist/dawproject/DawProjectIO.d.ts.map +1 -0
- package/dist/dawproject/DawProjectIO.js +31 -0
- package/dist/dawproject/DawProjectImport.test.d.ts +2 -0
- package/dist/dawproject/DawProjectImport.test.d.ts.map +1 -0
- package/dist/dawproject/DawProjectImport.test.js +16 -0
- package/dist/dawproject/DawProjectImporter.d.ts +12 -0
- package/dist/dawproject/DawProjectImporter.d.ts.map +1 -0
- package/dist/dawproject/DawProjectImporter.js +273 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/processors.js +2 -2
- package/dist/processors.js.map +4 -4
- package/dist/workers.js +2 -2
- package/dist/workers.js.map +3 -3
- package/package.json +18 -18
- package/dist/DawProjectIO.d.ts +0 -5
- package/dist/DawProjectIO.d.ts.map +0 -1
- package/dist/DawProjectIO.js +0 -54
package/dist/Project.d.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import { Terminable, TerminableOwner, Terminator } from "@opendaw/lib-std";
|
2
2
|
import { BoxGraph, Editing } from "@opendaw/lib-box";
|
3
3
|
import { AudioBusBox, AudioUnitBox, BoxIO, RootBox, TimelineBox, UserInterfaceBox } from "@opendaw/studio-boxes";
|
4
|
-
import { BoxAdapters, BoxAdaptersContext, ClipSequencing, ParameterFieldAdapters, RootBoxAdapter, SampleManager, TimelineBoxAdapter, UserEditingManager, VertexSelection } from "@opendaw/studio-adapters";
|
4
|
+
import { BoxAdapters, BoxAdaptersContext, ClipSequencing, ParameterFieldAdapters, ProjectDecoder, RootBoxAdapter, SampleManager, TimelineBoxAdapter, UserEditingManager, VertexSelection } from "@opendaw/studio-adapters";
|
5
5
|
import { LiveStreamBroadcaster, LiveStreamReceiver } from "@opendaw/lib-fusion";
|
6
6
|
import { ProjectEnv } from "./ProjectEnv";
|
7
7
|
import { Mixer } from "./Mixer";
|
@@ -10,6 +10,7 @@ export declare class Project implements BoxAdaptersContext, Terminable, Terminab
|
|
10
10
|
#private;
|
11
11
|
static new(env: ProjectEnv): Project;
|
12
12
|
static load(env: ProjectEnv, arrayBuffer: ArrayBuffer): Project;
|
13
|
+
static skeleton(env: ProjectEnv, skeleton: ProjectDecoder.Skeleton): Project;
|
13
14
|
readonly boxGraph: BoxGraph<BoxIO.TypeMap>;
|
14
15
|
readonly rootBox: RootBox;
|
15
16
|
readonly userInterfaceBox: UserInterfaceBox;
|
@@ -36,6 +37,7 @@ export declare class Project implements BoxAdaptersContext, Terminable, Terminab
|
|
36
37
|
get isAudioContext(): boolean;
|
37
38
|
get isMainThread(): boolean;
|
38
39
|
get liveStreamBroadcaster(): LiveStreamBroadcaster;
|
40
|
+
get skeleton(): ProjectDecoder.Skeleton;
|
39
41
|
toArrayBuffer(): ArrayBufferLike;
|
40
42
|
copy(): Project;
|
41
43
|
terminate(): void;
|
package/dist/Project.d.ts.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"Project.d.ts","sourceRoot":"","sources":["../src/Project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,UAAU,EAAE,eAAe,EAAE,UAAU,EAAO,MAAM,kBAAkB,CAAA;AAC9G,OAAO,EAAC,QAAQ,EAAE,OAAO,EAAC,MAAM,kBAAkB,CAAA;AAClD,OAAO,EACH,WAAW,EACX,YAAY,EACZ,KAAK,EAEL,OAAO,EACP,WAAW,EACX,gBAAgB,EACnB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,WAAW,EACX,kBAAkB,EAClB,cAAc,EAGd,sBAAsB,
|
1
|
+
{"version":3,"file":"Project.d.ts","sourceRoot":"","sources":["../src/Project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,UAAU,EAAE,eAAe,EAAE,UAAU,EAAO,MAAM,kBAAkB,CAAA;AAC9G,OAAO,EAAC,QAAQ,EAAE,OAAO,EAAC,MAAM,kBAAkB,CAAA;AAClD,OAAO,EACH,WAAW,EACX,YAAY,EACZ,KAAK,EAEL,OAAO,EACP,WAAW,EACX,gBAAgB,EACnB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,WAAW,EACX,kBAAkB,EAClB,cAAc,EAGd,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EAClB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAC,qBAAqB,EAAE,kBAAkB,EAAC,MAAM,qBAAqB,CAAA;AAE7E,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAA;AACvC,OAAO,EAAC,KAAK,EAAC,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAA;AAKvC,qBAAa,OAAQ,YAAW,kBAAkB,EAAE,UAAU,EAAE,eAAe;;IAC3E,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO;IAuCpC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,GAAG,OAAO;IAM/D,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,CAAC,QAAQ,GAAG,OAAO;IAQ5E,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IAE1C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAA;IAC3C,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAA;IAClC,QAAQ,CAAC,eAAe,EAAE,YAAY,CAAA;IACtC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IAEjC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAA;IACnC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAA;IAC/C,QAAQ,CAAC,sBAAsB,EAAE,sBAAsB,CAAA;IACvD,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAA;IAC/C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IAErB,OAAO;IA8BP,GAAG,CAAC,CAAC,SAAS,UAAU,EAAE,UAAU,EAAE,CAAC,GAAG,CAAC;IAC3C,MAAM,CAAC,CAAC,SAAS,UAAU,EAAE,GAAG,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI;IAC5D,KAAK,IAAI,UAAU;IAEnB,IAAI,GAAG,IAAI,MAAM,CAAyC;IAC1D,IAAI,cAAc,IAAI,cAAc,CAAmE;IACvG,IAAI,kBAAkB,IAAI,kBAAkB,CAA2E;IACvH,IAAI,YAAY,IAAI,aAAa,CAAiC;IAClE,IAAI,cAAc,IAAI,cAAc,CAAkD;IACtF,IAAI,cAAc,IAAI,OAAO,CAAe;IAC5C,IAAI,YAAY,IAAI,OAAO,CAAc;IACzC,IAAI,qBAAqB,IAAI,qBAAqB,CAAkD;IAEpG,IAAI,QAAQ,IAAI,cAAc,CAAC,QAAQ,CAWtC;IAED,aAAa,IAAI,eAAe;IAiBhC,IAAI,IAAI,OAAO;IAEf,SAAS,IAAI,IAAI;IAEjB,YAAY,IAAI,MAAM;CACzB"}
|
package/dist/Project.js
CHANGED
@@ -7,7 +7,6 @@ import { AudioUnitType } from "@opendaw/studio-enums";
|
|
7
7
|
import { Mixer } from "./Mixer";
|
8
8
|
import { ProjectApi } from "./ProjectApi";
|
9
9
|
import { ProjectMigration } from "./ProjectMigration";
|
10
|
-
import { DawProjectIO } from "./DawProjectIO";
|
11
10
|
// Main Entry Point for a Project
|
12
11
|
//
|
13
12
|
export class Project {
|
@@ -54,6 +53,10 @@ export class Project {
|
|
54
53
|
ProjectMigration.migrate(skeleton);
|
55
54
|
return new Project(env, skeleton.boxGraph, skeleton.mandatoryBoxes);
|
56
55
|
}
|
56
|
+
static skeleton(env, skeleton) {
|
57
|
+
ProjectMigration.migrate(skeleton);
|
58
|
+
return new Project(env, skeleton.boxGraph, skeleton.mandatoryBoxes);
|
59
|
+
}
|
57
60
|
#terminator = new Terminator();
|
58
61
|
#env;
|
59
62
|
boxGraph;
|
@@ -101,6 +104,18 @@ export class Project {
|
|
101
104
|
get isAudioContext() { return false; }
|
102
105
|
get isMainThread() { return true; }
|
103
106
|
get liveStreamBroadcaster() { return panic("Only available in audio context"); }
|
107
|
+
get skeleton() {
|
108
|
+
return {
|
109
|
+
boxGraph: this.boxGraph,
|
110
|
+
mandatoryBoxes: {
|
111
|
+
rootBox: this.rootBox,
|
112
|
+
timelineBox: this.timelineBox,
|
113
|
+
masterBusBox: this.masterBusBox,
|
114
|
+
masterAudioUnit: this.masterAudioUnit,
|
115
|
+
userInterfaceBox: this.userInterfaceBox
|
116
|
+
}
|
117
|
+
};
|
118
|
+
}
|
104
119
|
toArrayBuffer() {
|
105
120
|
const output = ByteArrayOutput.create();
|
106
121
|
output.writeInt(ProjectDecoder.MAGIC_HEADER_OPEN);
|
@@ -119,5 +134,5 @@ export class Project {
|
|
119
134
|
}
|
120
135
|
copy() { return Project.load(this.#env, this.toArrayBuffer()); }
|
121
136
|
terminate() { this.#terminator.terminate(); }
|
122
|
-
toDawProject() { return
|
137
|
+
toDawProject() { return panic("Not implemented"); }
|
123
138
|
}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import { ProjectDecoder } from "@opendaw/studio-adapters";
|
2
|
+
export declare class DawProjectExporter {
|
3
|
+
#private;
|
4
|
+
static exportProject(skeleton: ProjectDecoder.Skeleton): DawProjectExporter;
|
5
|
+
constructor(skeleton: ProjectDecoder.Skeleton);
|
6
|
+
toProjectXml(): string;
|
7
|
+
}
|
8
|
+
//# sourceMappingURL=DawProjectExporter.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"DawProjectExporter.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectExporter.ts"],"names":[],"mappings":"AASA,OAAO,EAAC,cAAc,EAAC,MAAM,0BAA0B,CAAA;AAEvD,qBAAa,kBAAkB;;IAC3B,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,cAAc,CAAC,QAAQ,GAAG,kBAAkB;gBAM/D,QAAQ,EAAE,cAAc,CAAC,QAAQ;IAE7C,YAAY,IAAI,MAAM;CAoBzB"}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import { Xml } from "@opendaw/lib-xml";
|
2
|
+
import { ApplicationSchema, ArrangementSchema, ProjectSchema, RealParameterSchema, TransportSchema, Unit } from "@opendaw/lib-dawproject";
|
3
|
+
export class DawProjectExporter {
|
4
|
+
static exportProject(skeleton) {
|
5
|
+
return new DawProjectExporter(skeleton);
|
6
|
+
}
|
7
|
+
#skeleton;
|
8
|
+
constructor(skeleton) { this.#skeleton = skeleton; }
|
9
|
+
toProjectXml() {
|
10
|
+
const { rootBox, timelineBox } = this.#skeleton.mandatoryBoxes;
|
11
|
+
const element = Xml.toElement("Project", Xml.element({
|
12
|
+
version: "1.0",
|
13
|
+
application: Xml.element({
|
14
|
+
name: "openDAW",
|
15
|
+
version: "0.1"
|
16
|
+
}, ApplicationSchema),
|
17
|
+
transport: Xml.element({
|
18
|
+
tempo: Xml.element({
|
19
|
+
value: timelineBox.bpm.getValue(),
|
20
|
+
unit: Unit.BPM
|
21
|
+
}, RealParameterSchema)
|
22
|
+
}, TransportSchema),
|
23
|
+
structure: [],
|
24
|
+
arrangement: Xml.element({}, ArrangementSchema),
|
25
|
+
scenes: []
|
26
|
+
}, ProjectSchema));
|
27
|
+
return Xml.pretty(element);
|
28
|
+
}
|
29
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"DawProjectExporter.test.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectExporter.test.ts"],"names":[],"mappings":""}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { describe, it } from "vitest";
|
2
|
+
import { fileURLToPath } from "url";
|
3
|
+
import * as path from "node:path";
|
4
|
+
import * as fs from "node:fs";
|
5
|
+
import { ProjectDecoder } from "@opendaw/studio-adapters";
|
6
|
+
import { ProjectMigration } from "../ProjectMigration";
|
7
|
+
import { DawProjectExporter } from "./DawProjectExporter";
|
8
|
+
describe("DawProjectExport", () => {
|
9
|
+
it("export", async () => {
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
11
|
+
const buffer = fs.readFileSync(path.join(__dirname, "../../../../../packages/app/studio/public/templates/Fatso.od"));
|
12
|
+
const skeleton = ProjectDecoder.decode(buffer.buffer);
|
13
|
+
ProjectMigration.migrate(skeleton);
|
14
|
+
console.debug(DawProjectExporter.exportProject(skeleton).toProjectXml());
|
15
|
+
});
|
16
|
+
});
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { UUID } from "@opendaw/lib-std";
|
2
|
+
import { MetaDataSchema, ProjectSchema } from "@opendaw/lib-dawproject";
|
3
|
+
export declare namespace DawProjectIO {
|
4
|
+
type Resource = {
|
5
|
+
uuid: UUID.Format;
|
6
|
+
path: string;
|
7
|
+
name: string;
|
8
|
+
buffer: ArrayBuffer;
|
9
|
+
};
|
10
|
+
interface Resources {
|
11
|
+
fromPath(path: string): Resource;
|
12
|
+
fromUUID(uuid: UUID.Format): Resource;
|
13
|
+
}
|
14
|
+
const decode: (buffer: ArrayBuffer | NonSharedBuffer) => Promise<{
|
15
|
+
metaData: MetaDataSchema;
|
16
|
+
project: ProjectSchema;
|
17
|
+
resources: Resources;
|
18
|
+
}>;
|
19
|
+
}
|
20
|
+
//# sourceMappingURL=DawProjectIO.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"DawProjectIO.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectIO.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,IAAI,EAAC,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAC,cAAc,EAAE,aAAa,EAAC,MAAM,yBAAyB,CAAA;AAErE,yBAAiB,YAAY,CAAC;IAC1B,KAAY,QAAQ,GAAG;QAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,WAAW,CAAA;KAAE,CAAA;IAE7F,UAAiB,SAAS;QACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAA;QAChC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAA;KACxC;IAEM,MAAM,MAAM,GAAU,QAAQ,WAAW,GAAG,eAAe,KAAG,OAAO,CAAC;QACzE,QAAQ,EAAE,cAAc,CAAC;QACzB,OAAO,EAAE,aAAa,CAAC;QACvB,SAAS,EAAE,SAAS,CAAA;KACvB,CAyBA,CAAA;CACJ"}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import JSZip from "jszip";
|
2
|
+
import { Xml } from "@opendaw/lib-xml";
|
3
|
+
import { asDefined, panic, UUID } from "@opendaw/lib-std";
|
4
|
+
import { MetaDataSchema, ProjectSchema } from "@opendaw/lib-dawproject";
|
5
|
+
export var DawProjectIO;
|
6
|
+
(function (DawProjectIO) {
|
7
|
+
DawProjectIO.decode = async (buffer) => {
|
8
|
+
const zip = await JSZip.loadAsync(buffer);
|
9
|
+
const metaData = Xml.parse(asDefined(await zip.file("metadata.xml")
|
10
|
+
?.async("string"), "No metadata.xml found"), MetaDataSchema);
|
11
|
+
const projectXml = asDefined(await zip.file("project.xml")
|
12
|
+
?.async("string"), "No project.xml found");
|
13
|
+
console.debug(projectXml);
|
14
|
+
const project = Xml.parse(projectXml, ProjectSchema);
|
15
|
+
const resourceFiles = Object.entries(zip.files).filter(([_, file]) => !file.dir && !file.name.endsWith(".xml"));
|
16
|
+
const resources = await Promise.all(resourceFiles.map(async ([path, file]) => {
|
17
|
+
const name = path.substring(path.lastIndexOf("/") + 1);
|
18
|
+
const buffer = await file.async("arraybuffer");
|
19
|
+
const uuid = await UUID.sha256(new Uint8Array(buffer).buffer);
|
20
|
+
return { uuid, path, name, buffer };
|
21
|
+
}));
|
22
|
+
return {
|
23
|
+
metaData, project, resources: {
|
24
|
+
fromPath: (path) => resources
|
25
|
+
.find(resource => resource.path === path) ?? panic("Resource not found"),
|
26
|
+
fromUUID: (uuid) => resources
|
27
|
+
.find(resource => UUID.equals(resource.uuid, uuid)) ?? panic("Resource not found")
|
28
|
+
}
|
29
|
+
};
|
30
|
+
};
|
31
|
+
})(DawProjectIO || (DawProjectIO = {}));
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"DawProjectImport.test.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectImport.test.ts"],"names":[],"mappings":""}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { describe, it } from "vitest";
|
2
|
+
import { fileURLToPath } from "url";
|
3
|
+
import * as path from "node:path";
|
4
|
+
import * as fs from "node:fs";
|
5
|
+
import { DawProjectIO } from "./DawProjectIO";
|
6
|
+
import { DawProjectImporter } from "./DawProjectImporter";
|
7
|
+
describe("DawProjectImport", () => {
|
8
|
+
it("import", async () => {
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
10
|
+
const buffer = fs.readFileSync(path.join(__dirname, "../../../../../test-files/test.dawproject"));
|
11
|
+
const { project, resources } = await DawProjectIO.decode(buffer);
|
12
|
+
const importer = await DawProjectImporter.importProject(project, resources);
|
13
|
+
console.debug(importer.skeleton);
|
14
|
+
console.dir(project.structure[1].channel?.devices, { depth: Number.MAX_SAFE_INTEGER });
|
15
|
+
});
|
16
|
+
});
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { SortedSet, UUID } from "@opendaw/lib-std";
|
2
|
+
import { ProjectSchema } from "@opendaw/lib-dawproject";
|
3
|
+
import { ProjectDecoder } from "@opendaw/studio-adapters";
|
4
|
+
import { DawProjectIO } from "./DawProjectIO";
|
5
|
+
export declare class DawProjectImporter {
|
6
|
+
#private;
|
7
|
+
static importProject(schema: ProjectSchema, resources: DawProjectIO.Resources): Promise<DawProjectImporter>;
|
8
|
+
private constructor();
|
9
|
+
get audioIDs(): SortedSet<UUID.Format, UUID.Format>;
|
10
|
+
get skeleton(): ProjectDecoder.Skeleton;
|
11
|
+
}
|
12
|
+
//# sourceMappingURL=DawProjectImporter.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"DawProjectImporter.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectImporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAWH,SAAS,EACT,IAAI,EAEP,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAQH,aAAa,EAMhB,MAAM,yBAAyB,CAAA;AAkBhC,OAAO,EAAa,cAAc,EAAY,MAAM,0BAA0B,CAAA;AAC9E,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAI3C,qBAAa,kBAAkB;;WACd,aAAa,CAAC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,YAAY,CAAC,SAAS,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAiB1F,OAAO;IAgDP,IAAI,QAAQ,IAAI,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAwB;IAC3E,IAAI,QAAQ,IAAI,cAAc,CAAC,QAAQ,CAWtC;CAwMJ"}
|
@@ -0,0 +1,273 @@
|
|
1
|
+
import { asDefined, assert, identity, isInstanceOf, isUndefined, Option, panic, UUID, ValueMapping } from "@opendaw/lib-std";
|
2
|
+
import { BoxGraph } from "@opendaw/lib-box";
|
3
|
+
import { gainToDb, PPQN } from "@opendaw/lib-dsp";
|
4
|
+
import { ClipsSchema, LanesSchema, NotesSchema, SendType, TrackSchema, WarpsSchema } from "@opendaw/lib-dawproject";
|
5
|
+
import { AudioSendRouting, AudioUnitType } from "@opendaw/studio-enums";
|
6
|
+
import { AudioBusBox, AudioFileBox, AudioRegionBox, AudioUnitBox, AuxSendBox, BoxIO, GrooveShuffleBox, NoteEventBox, NoteEventCollectionBox, NoteRegionBox, RootBox, TimelineBox, TrackBox, UserInterfaceBox } from "@opendaw/studio-boxes";
|
7
|
+
import { IconSymbol, TrackType } from "@opendaw/studio-adapters";
|
8
|
+
import { ColorCodes } from "../ColorCodes";
|
9
|
+
import { InstrumentFactories } from "../InstrumentFactories";
|
10
|
+
export class DawProjectImporter {
|
11
|
+
static async importProject(schema, resources) {
|
12
|
+
return new DawProjectImporter(schema, resources).#read();
|
13
|
+
}
|
14
|
+
#schema;
|
15
|
+
#resources;
|
16
|
+
#mapTrackBoxes;
|
17
|
+
#audioIDs;
|
18
|
+
#boxGraph;
|
19
|
+
#rootBox;
|
20
|
+
#masterBusBox;
|
21
|
+
#masterAudioUnit;
|
22
|
+
#timelineBox;
|
23
|
+
#userInterfaceBox;
|
24
|
+
constructor(schema, resources) {
|
25
|
+
this.#schema = schema;
|
26
|
+
this.#resources = resources;
|
27
|
+
this.#mapTrackBoxes = new Map();
|
28
|
+
this.#audioIDs = UUID.newSet(identity);
|
29
|
+
const isoString = new Date().toISOString();
|
30
|
+
console.debug(`New Project imported on ${isoString}`);
|
31
|
+
this.#boxGraph = new BoxGraph(Option.wrap(BoxIO.create));
|
32
|
+
this.#boxGraph.beginTransaction();
|
33
|
+
const grooveShuffleBox = GrooveShuffleBox.create(this.#boxGraph, UUID.generate(), box => {
|
34
|
+
box.label.setValue("Groove Shuffle");
|
35
|
+
});
|
36
|
+
this.#timelineBox = TimelineBox.create(this.#boxGraph, UUID.generate());
|
37
|
+
this.#rootBox = RootBox.create(this.#boxGraph, UUID.generate(), box => {
|
38
|
+
box.groove.refer(grooveShuffleBox);
|
39
|
+
box.created.setValue(isoString);
|
40
|
+
box.timeline.refer(this.#timelineBox.root);
|
41
|
+
});
|
42
|
+
this.#masterBusBox = AudioBusBox.create(this.#boxGraph, UUID.generate(), box => {
|
43
|
+
box.collection.refer(this.#rootBox.audioBusses);
|
44
|
+
box.label.setValue("Output");
|
45
|
+
box.icon.setValue(IconSymbol.toName(IconSymbol.SpeakerHeadphone));
|
46
|
+
box.color.setValue("hsl(189, 100%, 65%)");
|
47
|
+
});
|
48
|
+
this.#masterAudioUnit = AudioUnitBox.create(this.#boxGraph, UUID.generate(), box => {
|
49
|
+
box.type.setValue(AudioUnitType.Output);
|
50
|
+
box.collection.refer(this.#rootBox.audioUnits);
|
51
|
+
box.output.refer(this.#rootBox.outputDevice);
|
52
|
+
box.index.setValue(0);
|
53
|
+
});
|
54
|
+
this.#masterBusBox.output.refer(this.#masterAudioUnit.input);
|
55
|
+
this.#userInterfaceBox = UserInterfaceBox.create(this.#boxGraph, UUID.generate(), box => box.root.refer(this.#rootBox.users));
|
56
|
+
}
|
57
|
+
async #read() {
|
58
|
+
this.#readTransport();
|
59
|
+
this.#readStructure();
|
60
|
+
await this.#readArrangement();
|
61
|
+
this.#boxGraph.endTransaction();
|
62
|
+
this.#boxGraph.verifyPointers();
|
63
|
+
return this;
|
64
|
+
}
|
65
|
+
get audioIDs() { return this.#audioIDs; }
|
66
|
+
get skeleton() {
|
67
|
+
return {
|
68
|
+
boxGraph: this.#boxGraph,
|
69
|
+
mandatoryBoxes: {
|
70
|
+
rootBox: this.#rootBox,
|
71
|
+
timelineBox: this.#timelineBox,
|
72
|
+
masterBusBox: this.#masterBusBox,
|
73
|
+
masterAudioUnit: this.#masterAudioUnit,
|
74
|
+
userInterfaceBox: this.#userInterfaceBox
|
75
|
+
}
|
76
|
+
};
|
77
|
+
}
|
78
|
+
#readTransport() {
|
79
|
+
const { transport } = this.#schema;
|
80
|
+
if (isUndefined(transport)) {
|
81
|
+
return;
|
82
|
+
}
|
83
|
+
this.#timelineBox.bpm.setValue(transport.tempo?.value ?? 120.0);
|
84
|
+
this.#timelineBox.signature.nominator.setValue(transport.timeSignature?.numerator ?? 4);
|
85
|
+
this.#timelineBox.signature.denominator.setValue(transport.timeSignature?.denominator ?? 4);
|
86
|
+
}
|
87
|
+
#readStructure() {
|
88
|
+
const resolveSendTargets = [];
|
89
|
+
const effectTargetMap = new Map();
|
90
|
+
const readChannel = (audioUnitBox, channel) => {
|
91
|
+
audioUnitBox.volume.setValue(gainToDb(channel.volume?.value ?? 1.0));
|
92
|
+
audioUnitBox.panning.setValue(ValueMapping.bipolar().y(channel.pan?.value ?? 0.5));
|
93
|
+
audioUnitBox.mute.setValue(channel.mute?.value === true);
|
94
|
+
audioUnitBox.solo.setValue(channel.solo === true);
|
95
|
+
channel.sends?.forEach((send) => {
|
96
|
+
// bitwig does not set enabled if it is enabled 🤥
|
97
|
+
const enable = isUndefined(send?.enable?.value) || send.enable.value;
|
98
|
+
if (enable) {
|
99
|
+
resolveSendTargets.push(() => AuxSendBox.create(this.#boxGraph, UUID.generate(), box => {
|
100
|
+
const type = send.type;
|
101
|
+
const destination = asDefined(send.destination, "destination is undefined");
|
102
|
+
box.routing.setValue(type === SendType.PRE ? AudioSendRouting.Pre : AudioSendRouting.Post);
|
103
|
+
box.sendGain.setValue(gainToDb(send.volume?.value ?? 1.0));
|
104
|
+
box.sendPan.setValue(ValueMapping.bipolar().y(send.pan?.value ?? 0.5));
|
105
|
+
box.targetBus.refer(asDefined(effectTargetMap.get(destination), "Cannot find destination").input);
|
106
|
+
box.audioUnit.refer(audioUnitBox.auxSends);
|
107
|
+
}));
|
108
|
+
}
|
109
|
+
});
|
110
|
+
};
|
111
|
+
const { structure } = this.#schema;
|
112
|
+
let audioUnitIndex = 0;
|
113
|
+
structure.forEach((lane) => {
|
114
|
+
if (isInstanceOf(lane, TrackSchema)) {
|
115
|
+
const trackType = this.#contentToTrackType(lane.contentType);
|
116
|
+
const channel = asDefined(lane.channel, "Track has no Channel");
|
117
|
+
// TODO devices
|
118
|
+
if (channel.role === "regular") {
|
119
|
+
const audioUnitBox = AudioUnitBox.create(this.#boxGraph, UUID.generate(), box => {
|
120
|
+
box.index.setValue(audioUnitIndex);
|
121
|
+
box.type.setValue(AudioUnitType.Instrument);
|
122
|
+
box.output.refer(this.#masterBusBox.input);
|
123
|
+
box.collection.refer(this.#rootBox.audioUnits);
|
124
|
+
readChannel(box, channel);
|
125
|
+
});
|
126
|
+
const trackBox = TrackBox.create(this.#boxGraph, UUID.generate(), box => {
|
127
|
+
box.type.setValue(trackType);
|
128
|
+
box.index.setValue(0);
|
129
|
+
box.tracks.refer(audioUnitBox.tracks);
|
130
|
+
box.target.refer(audioUnitBox);
|
131
|
+
});
|
132
|
+
this.#mapTrackBoxes.set(asDefined(lane.id, "Track must have an id."), trackBox);
|
133
|
+
if (trackType === TrackType.Notes) {
|
134
|
+
InstrumentFactories.Vaporisateur
|
135
|
+
.create(this.#boxGraph, audioUnitBox.input, lane.name ?? "", IconSymbol.Piano);
|
136
|
+
}
|
137
|
+
else if (trackType === TrackType.Audio) {
|
138
|
+
InstrumentFactories.Tape
|
139
|
+
.create(this.#boxGraph, audioUnitBox.input, lane.name ?? "", IconSymbol.Waveform);
|
140
|
+
}
|
141
|
+
}
|
142
|
+
else if (channel.role === "effect") {
|
143
|
+
const audioUnitBox = AudioUnitBox.create(this.#boxGraph, UUID.generate(), box => {
|
144
|
+
box.index.setValue(audioUnitIndex);
|
145
|
+
box.type.setValue(AudioUnitType.Aux);
|
146
|
+
box.output.refer(this.#masterBusBox.input);
|
147
|
+
box.collection.refer(this.#rootBox.audioUnits);
|
148
|
+
readChannel(box, channel);
|
149
|
+
});
|
150
|
+
const audioBusBox = AudioBusBox.create(this.#boxGraph, UUID.generate(), box => {
|
151
|
+
box.collection.refer(this.#rootBox.audioBusses);
|
152
|
+
box.label.setValue(lane.name ?? "Aux");
|
153
|
+
box.color.setValue(ColorCodes.forAudioType(AudioUnitType.Aux));
|
154
|
+
box.icon.setValue(IconSymbol.toName(IconSymbol.Effects));
|
155
|
+
box.output.refer(audioUnitBox.input);
|
156
|
+
});
|
157
|
+
const trackBox = TrackBox.create(this.#boxGraph, UUID.generate(), box => {
|
158
|
+
box.type.setValue(TrackType.Undefined);
|
159
|
+
box.index.setValue(0);
|
160
|
+
box.tracks.refer(audioUnitBox.tracks);
|
161
|
+
box.target.refer(audioUnitBox);
|
162
|
+
});
|
163
|
+
effectTargetMap.set(asDefined(channel.id, "Effect-channel must have id."), audioBusBox);
|
164
|
+
this.#mapTrackBoxes.set(asDefined(lane.id, "Track must have an id."), trackBox);
|
165
|
+
}
|
166
|
+
else if (channel.role === "master") {
|
167
|
+
readChannel(this.#masterAudioUnit, channel);
|
168
|
+
}
|
169
|
+
else {
|
170
|
+
return panic(`Unknown channel role: ${channel.role}`);
|
171
|
+
}
|
172
|
+
audioUnitIndex++;
|
173
|
+
}
|
174
|
+
});
|
175
|
+
this.#masterAudioUnit.index.setValue(audioUnitIndex);
|
176
|
+
resolveSendTargets.forEach(exec => exec());
|
177
|
+
resolveSendTargets.length = 0;
|
178
|
+
}
|
179
|
+
#readArrangement() {
|
180
|
+
const { arrangement } = this.#schema;
|
181
|
+
const readRegions = ({ clips }, track) => Promise.all(clips.map(clip => readAnyRegion(clip, track)));
|
182
|
+
const readLane = (lane) => {
|
183
|
+
const track = lane.track; // links to track in structure
|
184
|
+
return Promise.all(lane?.lanes?.filter(timeline => isInstanceOf(timeline, ClipsSchema))
|
185
|
+
.map(clips => readRegions(clips, asDefined(track, "Region(Clips) must have an id."))) ?? []);
|
186
|
+
};
|
187
|
+
const readAnyRegion = (clip, trackId) => {
|
188
|
+
const trackBox = asDefined(this.#mapTrackBoxes.get(trackId), `Could not find track for ${trackId}`);
|
189
|
+
return Promise.all(clip.content?.map(async (content) => {
|
190
|
+
if (isInstanceOf(content, ClipsSchema)) {
|
191
|
+
await readAnyRegionContent(clip, content, trackBox);
|
192
|
+
}
|
193
|
+
else if (isInstanceOf(content, NotesSchema)) {
|
194
|
+
readNoteRegionContent(clip, content, trackBox);
|
195
|
+
}
|
196
|
+
}) ?? []);
|
197
|
+
};
|
198
|
+
const readNoteRegionContent = (clip, notes, trackBox) => {
|
199
|
+
const collectionBox = NoteEventCollectionBox.create(this.#boxGraph, UUID.generate());
|
200
|
+
NoteRegionBox.create(this.#boxGraph, UUID.generate(), box => {
|
201
|
+
const position = asDefined(clip.time, "Time not defined");
|
202
|
+
const duration = asDefined(clip.duration, "Duration not defined");
|
203
|
+
const loopDuration = clip.loopEnd ?? duration;
|
204
|
+
box.position.setValue(position * PPQN.Quarter);
|
205
|
+
box.duration.setValue(duration * PPQN.Quarter);
|
206
|
+
box.label.setValue(clip.name ?? "");
|
207
|
+
box.loopDuration.setValue(loopDuration * PPQN.Quarter);
|
208
|
+
box.mute.setValue(clip.enable === false);
|
209
|
+
box.events.refer(collectionBox.owners);
|
210
|
+
box.regions.refer(trackBox.regions);
|
211
|
+
});
|
212
|
+
notes.notes?.forEach(note => {
|
213
|
+
NoteEventBox.create(this.#boxGraph, UUID.generate(), box => {
|
214
|
+
box.position.setValue(note.time * PPQN.Quarter);
|
215
|
+
box.duration.setValue(note.duration * PPQN.Quarter);
|
216
|
+
box.pitch.setValue(note.key);
|
217
|
+
box.velocity.setValue(note.vel ?? 1.0);
|
218
|
+
box.events.refer(collectionBox.events);
|
219
|
+
});
|
220
|
+
});
|
221
|
+
};
|
222
|
+
const readAnyRegionContent = async (clip, content, trackBox) => {
|
223
|
+
const contentClip = content.clips.at(0);
|
224
|
+
if (isUndefined(contentClip)) {
|
225
|
+
console.warn(clip, "audio-clip without content-clip?");
|
226
|
+
return;
|
227
|
+
}
|
228
|
+
const innerContent = contentClip.content?.at(0);
|
229
|
+
// TODO Double-check: From which point is it guaranteed that this is an audio region?
|
230
|
+
if (isInstanceOf(innerContent, WarpsSchema)) {
|
231
|
+
const { warps, content } = innerContent;
|
232
|
+
const audio = content?.at(0);
|
233
|
+
const warp0 = warps.at(0);
|
234
|
+
const warpN = warps.at(-1);
|
235
|
+
const warpDistance = asDefined(warpN?.time) - asDefined(warp0?.time);
|
236
|
+
if (isUndefined(audio)) {
|
237
|
+
return;
|
238
|
+
}
|
239
|
+
const { path, external } = audio.file;
|
240
|
+
assert(external !== true, "File cannot be external");
|
241
|
+
const { uuid, name } = this.#resources.fromPath(path);
|
242
|
+
const audioFileBox = this.#boxGraph.findBox(uuid)
|
243
|
+
.unwrapOrElse(() => AudioFileBox.create(this.#boxGraph, uuid, box => box.fileName.setValue(name)));
|
244
|
+
this.#audioIDs.add(uuid, true);
|
245
|
+
AudioRegionBox.create(this.#boxGraph, UUID.generate(), box => {
|
246
|
+
const position = asDefined(clip.time, "Time not defined");
|
247
|
+
const duration = asDefined(clip.duration, "Duration not defined");
|
248
|
+
const loopDuration = clip.loopEnd ?? warpDistance;
|
249
|
+
box.position.setValue(position * PPQN.Quarter);
|
250
|
+
box.duration.setValue(duration * PPQN.Quarter);
|
251
|
+
box.label.setValue(clip.name ?? "");
|
252
|
+
box.loopOffset.setValue((-(contentClip.time ?? 0.0)) * PPQN.Quarter);
|
253
|
+
box.loopDuration.setValue(loopDuration * PPQN.Quarter);
|
254
|
+
box.mute.setValue(clip.enable === false);
|
255
|
+
box.regions.refer(trackBox.regions);
|
256
|
+
box.file.refer(audioFileBox);
|
257
|
+
});
|
258
|
+
}
|
259
|
+
};
|
260
|
+
return Promise.all(arrangement?.lanes?.lanes?.filter(timeline => isInstanceOf(timeline, LanesSchema))
|
261
|
+
.map(readLane) ?? []);
|
262
|
+
}
|
263
|
+
#contentToTrackType(contentType) {
|
264
|
+
switch (contentType) {
|
265
|
+
case "audio":
|
266
|
+
return TrackType.Audio;
|
267
|
+
case "notes":
|
268
|
+
return TrackType.Notes;
|
269
|
+
default:
|
270
|
+
return TrackType.Undefined;
|
271
|
+
}
|
272
|
+
}
|
273
|
+
}
|
package/dist/index.d.ts
CHANGED
@@ -6,9 +6,11 @@ export * from "./samples/SampleStorage";
|
|
6
6
|
export * from "./samples/MainThreadSampleLoader";
|
7
7
|
export * from "./samples/MainThreadSampleManager";
|
8
8
|
export * from "./samples/SampleProvider";
|
9
|
+
export * from "./dawproject/DawProjectIO";
|
10
|
+
export * from "./dawproject/DawProjectExporter";
|
11
|
+
export * from "./dawproject/DawProjectImporter";
|
9
12
|
export * from "./ColorCodes";
|
10
13
|
export * from "./Colors";
|
11
|
-
export * from "./DawProjectIO";
|
12
14
|
export * from "./EffectBox";
|
13
15
|
export * from "./EffectFactory";
|
14
16
|
export * from "./EffectFactories";
|
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,mBAAmB,CAAA;AACjC,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA;AAExC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,kCAAkC,CAAA;AAChD,cAAc,mCAAmC,CAAA;AACjD,cAAc,0BAA0B,CAAA;AAExC,cAAc,cAAc,CAAA;
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA;AAExC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,kCAAkC,CAAA;AAChD,cAAc,mCAAmC,CAAA;AACjD,cAAc,0BAA0B,CAAA;AAExC,cAAc,2BAA2B,CAAA;AACzC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,iCAAiC,CAAA;AAE/C,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,iBAAiB,CAAA;AAC/B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,OAAO,CAAA;AACrB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA"}
|
package/dist/index.js
CHANGED
@@ -6,9 +6,11 @@ export * from "./samples/SampleStorage";
|
|
6
6
|
export * from "./samples/MainThreadSampleLoader";
|
7
7
|
export * from "./samples/MainThreadSampleManager";
|
8
8
|
export * from "./samples/SampleProvider";
|
9
|
+
export * from "./dawproject/DawProjectIO";
|
10
|
+
export * from "./dawproject/DawProjectExporter";
|
11
|
+
export * from "./dawproject/DawProjectImporter";
|
9
12
|
export * from "./ColorCodes";
|
10
13
|
export * from "./Colors";
|
11
|
-
export * from "./DawProjectIO";
|
12
14
|
export * from "./EffectBox";
|
13
15
|
export * from "./EffectFactory";
|
14
16
|
export * from "./EffectFactories";
|