@opendaw/studio-core 0.0.18 → 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 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;
@@ -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,EAEtB,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;AAMvC,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;IAS/D,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,aAAa,IAAI,eAAe;IAiBhC,IAAI,IAAI,OAAO;IAEf,SAAS,IAAI,IAAI;IAEjB,YAAY,IAAI,MAAM;CACzB"}
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 DawProjectIO.encode(this); }
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=DawProjectExporter.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=DawProjectImport.test.d.ts.map
@@ -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";
@@ -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;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,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"}
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";