@opendaw/studio-core 0.0.19 → 0.0.21

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 (110) hide show
  1. package/dist/AudioDevices.d.ts +8 -0
  2. package/dist/AudioDevices.d.ts.map +1 -0
  3. package/dist/AudioDevices.js +34 -0
  4. package/dist/AudioUnitOrdering.d.ts +3 -0
  5. package/dist/AudioUnitOrdering.d.ts.map +1 -0
  6. package/dist/AudioUnitOrdering.js +7 -0
  7. package/dist/EffectBox.d.ts +2 -2
  8. package/dist/EffectBox.d.ts.map +1 -1
  9. package/dist/Engine.d.ts +31 -10
  10. package/dist/Engine.d.ts.map +1 -1
  11. package/dist/EngineFacade.d.ts +22 -11
  12. package/dist/EngineFacade.d.ts.map +1 -1
  13. package/dist/EngineFacade.js +39 -22
  14. package/dist/EngineWorklet.d.ts +22 -13
  15. package/dist/EngineWorklet.d.ts.map +1 -1
  16. package/dist/EngineWorklet.js +47 -56
  17. package/dist/MeterWorklet.d.ts.map +1 -1
  18. package/dist/MeterWorklet.js +2 -2
  19. package/dist/MidiDevices.d.ts +12 -0
  20. package/dist/MidiDevices.d.ts.map +1 -0
  21. package/dist/MidiDevices.js +92 -0
  22. package/dist/Project.d.ts +14 -3
  23. package/dist/Project.d.ts.map +1 -1
  24. package/dist/Project.js +27 -4
  25. package/dist/ProjectApi.d.ts +0 -1
  26. package/dist/ProjectApi.d.ts.map +1 -1
  27. package/dist/ProjectApi.js +16 -11
  28. package/dist/ProjectMigration.d.ts.map +1 -1
  29. package/dist/ProjectMigration.js +20 -3
  30. package/dist/RecordingWorklet.d.ts +15 -3
  31. package/dist/RecordingWorklet.d.ts.map +1 -1
  32. package/dist/RecordingWorklet.js +111 -16
  33. package/dist/WorkerAgents.d.ts +2 -2
  34. package/dist/WorkerAgents.d.ts.map +1 -1
  35. package/dist/Worklets.d.ts +1 -1
  36. package/dist/Worklets.d.ts.map +1 -1
  37. package/dist/Worklets.js +2 -2
  38. package/dist/capture/Capture.d.ts +22 -0
  39. package/dist/capture/Capture.d.ts.map +1 -0
  40. package/dist/capture/Capture.js +32 -0
  41. package/dist/capture/CaptureAudio.d.ts +17 -0
  42. package/dist/capture/CaptureAudio.d.ts.map +1 -0
  43. package/dist/capture/CaptureAudio.js +106 -0
  44. package/dist/capture/CaptureManager.d.ts +12 -0
  45. package/dist/capture/CaptureManager.d.ts.map +1 -0
  46. package/dist/capture/CaptureManager.js +38 -0
  47. package/dist/capture/CaptureMidi.d.ts +13 -0
  48. package/dist/capture/CaptureMidi.d.ts.map +1 -0
  49. package/dist/capture/CaptureMidi.js +106 -0
  50. package/dist/capture/RecordAudio.d.ts +19 -0
  51. package/dist/capture/RecordAudio.d.ts.map +1 -0
  52. package/dist/capture/RecordAudio.js +66 -0
  53. package/dist/capture/RecordMidi.d.ts +13 -0
  54. package/dist/capture/RecordMidi.d.ts.map +1 -0
  55. package/dist/capture/RecordMidi.js +85 -0
  56. package/dist/capture/RecordTrack.d.ts +7 -0
  57. package/dist/capture/RecordTrack.d.ts.map +1 -0
  58. package/dist/capture/RecordTrack.js +23 -0
  59. package/dist/capture/Recording.d.ts +9 -0
  60. package/dist/capture/Recording.d.ts.map +1 -0
  61. package/dist/capture/Recording.js +65 -0
  62. package/dist/capture/RecordingContext.d.ts +10 -0
  63. package/dist/capture/RecordingContext.d.ts.map +1 -0
  64. package/dist/capture/RecordingContext.js +1 -0
  65. package/dist/dawproject/AudioUnitExportLayout.d.ts +10 -0
  66. package/dist/dawproject/AudioUnitExportLayout.d.ts.map +1 -0
  67. package/dist/dawproject/AudioUnitExportLayout.js +64 -0
  68. package/dist/dawproject/BuiltinDevices.d.ts +9 -0
  69. package/dist/dawproject/BuiltinDevices.d.ts.map +1 -0
  70. package/dist/dawproject/BuiltinDevices.js +70 -0
  71. package/dist/dawproject/{DawProjectIO.d.ts → DawProject.d.ts} +6 -4
  72. package/dist/dawproject/DawProject.d.ts.map +1 -0
  73. package/dist/dawproject/DawProject.js +48 -0
  74. package/dist/dawproject/DawProjectExporter.d.ts +7 -6
  75. package/dist/dawproject/DawProjectExporter.d.ts.map +1 -1
  76. package/dist/dawproject/DawProjectExporter.js +245 -22
  77. package/dist/dawproject/DawProjectExporter.test.js +38 -6
  78. package/dist/dawproject/DawProjectImport.d.ts +12 -0
  79. package/dist/dawproject/DawProjectImport.d.ts.map +1 -0
  80. package/dist/dawproject/DawProjectImport.js +389 -0
  81. package/dist/dawproject/DawProjectImport.test.js +6 -7
  82. package/dist/dawproject/DeviceIO.d.ts +8 -0
  83. package/dist/dawproject/DeviceIO.d.ts.map +1 -0
  84. package/dist/dawproject/DeviceIO.js +63 -0
  85. package/dist/index.d.ts +13 -3
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +13 -3
  88. package/dist/processors.js +3 -3
  89. package/dist/processors.js.map +4 -4
  90. package/dist/samples/MainThreadSampleLoader.d.ts +1 -0
  91. package/dist/samples/MainThreadSampleLoader.d.ts.map +1 -1
  92. package/dist/samples/MainThreadSampleLoader.js +10 -6
  93. package/dist/samples/MainThreadSampleManager.d.ts +5 -5
  94. package/dist/samples/MainThreadSampleManager.d.ts.map +1 -1
  95. package/dist/samples/MainThreadSampleManager.js +1 -0
  96. package/dist/samples/SampleProvider.d.ts +2 -2
  97. package/dist/samples/SampleProvider.d.ts.map +1 -1
  98. package/dist/samples/SampleStorage.d.ts.map +1 -1
  99. package/dist/samples/SampleStorage.js +2 -3
  100. package/dist/workers.js +2 -2
  101. package/dist/workers.js.map +4 -4
  102. package/package.json +15 -15
  103. package/dist/dawproject/DawProjectIO.d.ts.map +0 -1
  104. package/dist/dawproject/DawProjectIO.js +0 -31
  105. package/dist/dawproject/DawProjectImporter.d.ts +0 -12
  106. package/dist/dawproject/DawProjectImporter.d.ts.map +0 -1
  107. package/dist/dawproject/DawProjectImporter.js +0 -273
  108. package/dist/samples/SamplePeaks.d.ts +0 -6
  109. package/dist/samples/SamplePeaks.d.ts.map +0 -1
  110. package/dist/samples/SamplePeaks.js +0 -9
@@ -0,0 +1,10 @@
1
+ import { SampleManager } from "@opendaw/studio-adapters";
2
+ import { Project } from "../Project";
3
+ import { Worklets } from "../Worklets";
4
+ export interface RecordingContext {
5
+ project: Project;
6
+ worklets: Worklets;
7
+ audioContext: AudioContext;
8
+ sampleManager: SampleManager;
9
+ }
10
+ //# sourceMappingURL=RecordingContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecordingContext.d.ts","sourceRoot":"","sources":["../../src/capture/RecordingContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAC,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAClC,OAAO,EAAC,QAAQ,EAAC,MAAM,aAAa,CAAA;AAEpC,MAAM,WAAW,gBAAgB;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,QAAQ,CAAA;IAClB,YAAY,EAAE,YAAY,CAAA;IAC1B,aAAa,EAAE,aAAa,CAAA;CAC/B"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { AudioUnitBox } from "@opendaw/studio-boxes";
2
+ export declare namespace AudioUnitExportLayout {
3
+ interface Track {
4
+ audioUnit: AudioUnitBox;
5
+ children: Array<Track>;
6
+ }
7
+ const layout: (audioUnits: ReadonlyArray<AudioUnitBox>) => Array<Track>;
8
+ const printTrackStructure: (tracks: ReadonlyArray<Track>, indent?: number) => void;
9
+ }
10
+ //# sourceMappingURL=AudioUnitExportLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudioUnitExportLayout.d.ts","sourceRoot":"","sources":["../../src/dawproject/AudioUnitExportLayout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,YAAY,EAAC,MAAM,uBAAuB,CAAA;AAK/D,yBAAiB,qBAAqB,CAAC;IACnC,UAAiB,KAAK;QAClB,SAAS,EAAE,YAAY,CAAA;QACvB,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;KACzB;IAEM,MAAM,MAAM,GAAI,YAAY,aAAa,CAAC,YAAY,CAAC,KAAG,KAAK,CAAC,KAAK,CAgC3E,CAAA;IAgBM,MAAM,mBAAmB,GAAI,QAAQ,aAAa,CAAC,KAAK,CAAC,EAAE,eAAU,KAAG,IAU9E,CAAA;CACJ"}
@@ -0,0 +1,64 @@
1
+ import { AudioBusBox, AudioUnitBox } from "@opendaw/studio-boxes";
2
+ import { ArrayMultimap, asInstanceOf, isDefined, isInstanceOf, Option } from "@opendaw/lib-std";
3
+ import { AudioUnitType } from "@opendaw/studio-enums";
4
+ import { DeviceBoxUtils } from "@opendaw/studio-adapters";
5
+ export var AudioUnitExportLayout;
6
+ (function (AudioUnitExportLayout) {
7
+ AudioUnitExportLayout.layout = (audioUnits) => {
8
+ const feedsInto = new ArrayMultimap();
9
+ audioUnits.forEach(unit => {
10
+ unit.output.targetVertex.ifSome(({ box }) => {
11
+ if (isInstanceOf(box, AudioBusBox)) {
12
+ box.output.targetVertex.ifSome(({ box: targetUnit }) => {
13
+ const audioUnit = asInstanceOf(targetUnit, AudioUnitBox);
14
+ if (audioUnit.type.getValue() !== AudioUnitType.Output) {
15
+ feedsInto.add(audioUnit, unit);
16
+ }
17
+ });
18
+ }
19
+ });
20
+ });
21
+ // Roots are:
22
+ // 1. Units with no output
23
+ // 2. Units that connect directly to Output (become independent roots)
24
+ // 3. The Output unit itself (as a standalone root)
25
+ const roots = audioUnits.filter(unit => {
26
+ if (unit.type.getValue() === AudioUnitType.Output) {
27
+ return true;
28
+ }
29
+ if (unit.output.targetVertex.isEmpty()) {
30
+ return true;
31
+ }
32
+ return unit.output.targetVertex
33
+ .flatMap(({ box }) => isInstanceOf(box, AudioBusBox) ? box.output.targetVertex : Option.None)
34
+ .map(({ box }) => asInstanceOf(box, AudioUnitBox).type.getValue() === AudioUnitType.Output)
35
+ .unwrapOrElse(false);
36
+ });
37
+ const visited = new Set();
38
+ return roots
39
+ .map(root => buildTrackRecursive(root, feedsInto, visited))
40
+ .filter(isDefined);
41
+ };
42
+ const buildTrackRecursive = (audioUnit, feedsInto, visited) => {
43
+ if (visited.has(audioUnit)) {
44
+ console.warn(`Cycle detected at AudioUnitBox`, audioUnit);
45
+ return null;
46
+ }
47
+ visited.add(audioUnit);
48
+ const children = feedsInto.get(audioUnit)
49
+ .map(childUnit => buildTrackRecursive(childUnit, feedsInto, visited))
50
+ .filter(isDefined);
51
+ return { audioUnit, children };
52
+ };
53
+ AudioUnitExportLayout.printTrackStructure = (tracks, indent = 0) => {
54
+ const spaces = " ".repeat(indent);
55
+ tracks.forEach(track => {
56
+ const inputBox = track.audioUnit.input.pointerHub.incoming().at(0)?.box;
57
+ const label = DeviceBoxUtils.lookupLabelField(inputBox).getValue();
58
+ console.debug(`${spaces}⌙ ${label} (${track.audioUnit.address.toString()})`);
59
+ if (track.children.length > 0) {
60
+ AudioUnitExportLayout.printTrackStructure(track.children, indent + 2);
61
+ }
62
+ });
63
+ };
64
+ })(AudioUnitExportLayout || (AudioUnitExportLayout = {}));
@@ -0,0 +1,9 @@
1
+ import { EqualizerSchema } from "@opendaw/lib-dawproject";
2
+ import { RevampDeviceBox } from "@opendaw/studio-boxes";
3
+ import { BoxGraph, Field } from "@opendaw/lib-box";
4
+ import { int } from "@opendaw/lib-std";
5
+ import { Pointers } from "@opendaw/studio-enums";
6
+ export declare namespace BuiltinDevices {
7
+ const equalizer: (boxGraph: BoxGraph, equalizer: EqualizerSchema, field: Field<Pointers.MidiEffectHost> | Field<Pointers.AudioEffectHost>, index: int) => RevampDeviceBox;
8
+ }
9
+ //# sourceMappingURL=BuiltinDevices.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BuiltinDevices.d.ts","sourceRoot":"","sources":["../../src/dawproject/BuiltinDevices.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,eAAe,EAAmB,MAAM,yBAAyB,CAAA;AACjG,OAAO,EAAC,eAAe,EAA0B,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAAC,QAAQ,EAAE,KAAK,EAAC,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAY,GAAG,EAAO,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAA;AAG9C,yBAAiB,cAAc,CAAC;IACrB,MAAM,SAAS,GAAI,UAAU,QAAQ,EAClB,WAAW,eAAe,EAC1B,OAAO,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,eAAe,CAAC,EACvE,OAAO,GAAG,KAAG,eA4DtC,CAAA;CACJ"}
@@ -0,0 +1,70 @@
1
+ import { EqBandType, ParameterDecoder } from "@opendaw/lib-dawproject";
2
+ import { RevampDeviceBox } from "@opendaw/studio-boxes";
3
+ import { ifDefined, UUID } from "@opendaw/lib-std";
4
+ import { semitoneToHz } from "@opendaw/lib-dsp";
5
+ export var BuiltinDevices;
6
+ (function (BuiltinDevices) {
7
+ BuiltinDevices.equalizer = (boxGraph, equalizer, field, index) => {
8
+ const mapOrder = (order) => {
9
+ switch (order) {
10
+ case 1:
11
+ case 2:
12
+ return 0;
13
+ case 3:
14
+ case 4:
15
+ return 1;
16
+ case 5:
17
+ case 6:
18
+ return 2;
19
+ default:
20
+ return 3;
21
+ }
22
+ };
23
+ const readPass = (schema, pass) => {
24
+ const { order, frequency, q, enabled } = pass;
25
+ order.setValue(mapOrder(schema.order));
26
+ frequency.setValue(semitoneToHz(schema.freq.value));
27
+ ifDefined(schema.Q?.value, value => q.setValue(value));
28
+ ifDefined(schema.enabled?.value, value => enabled.setValue(value));
29
+ };
30
+ const readShelf = (schema, pass) => {
31
+ const { frequency, gain, enabled } = pass;
32
+ frequency.setValue(ParameterDecoder.readValue(schema.freq));
33
+ ifDefined(schema.gain?.value, value => gain.setValue(value));
34
+ ifDefined(schema.enabled?.value, value => enabled.setValue(value));
35
+ };
36
+ return RevampDeviceBox.create(boxGraph, UUID.generate(), box => {
37
+ box.host.refer(field);
38
+ box.index.setValue(index);
39
+ box.label.setValue(equalizer.deviceName ?? "Revamp");
40
+ let bellIndex = 0;
41
+ equalizer.bands.forEach((band) => {
42
+ switch (band.type) {
43
+ case EqBandType.HIGH_PASS:
44
+ return readPass(band, box.highPass);
45
+ case EqBandType.LOW_PASS:
46
+ return readPass(band, box.lowPass);
47
+ case EqBandType.HIGH_SHELF:
48
+ return readShelf(band, box.highShelf);
49
+ case EqBandType.LOW_SHELF:
50
+ return readShelf(band, box.lowShelf);
51
+ case EqBandType.BELL: {
52
+ if (bellIndex === 3) {
53
+ return;
54
+ }
55
+ const bell = [box.lowBell, box.midBell, box.highBell][bellIndex++];
56
+ const { frequency, gain, q, enabled } = bell;
57
+ frequency.setValue(ParameterDecoder.readValue(band.freq));
58
+ ifDefined(band.Q?.value, value => q.setValue(value));
59
+ ifDefined(band.gain?.value, value => gain.setValue(value));
60
+ ifDefined(band.enabled?.value, value => enabled.setValue(value));
61
+ return;
62
+ }
63
+ default:
64
+ console.warn(`Cannot map band type: ${band.type} to Revamp`);
65
+ return;
66
+ }
67
+ });
68
+ });
69
+ };
70
+ })(BuiltinDevices || (BuiltinDevices = {}));
@@ -1,20 +1,22 @@
1
1
  import { UUID } from "@opendaw/lib-std";
2
2
  import { MetaDataSchema, ProjectSchema } from "@opendaw/lib-dawproject";
3
- export declare namespace DawProjectIO {
3
+ import { Project } from "../Project";
4
+ export declare namespace DawProject {
4
5
  type Resource = {
5
6
  uuid: UUID.Format;
6
7
  path: string;
7
8
  name: string;
8
9
  buffer: ArrayBuffer;
9
10
  };
10
- interface Resources {
11
+ interface ResourceProvider {
11
12
  fromPath(path: string): Resource;
12
13
  fromUUID(uuid: UUID.Format): Resource;
13
14
  }
14
15
  const decode: (buffer: ArrayBuffer | NonSharedBuffer) => Promise<{
15
16
  metaData: MetaDataSchema;
16
17
  project: ProjectSchema;
17
- resources: Resources;
18
+ resources: ResourceProvider;
18
19
  }>;
20
+ const encode: (project: Project, metaData: MetaDataSchema) => Promise<ArrayBuffer>;
19
21
  }
20
- //# sourceMappingURL=DawProjectIO.d.ts.map
22
+ //# sourceMappingURL=DawProject.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DawProject.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProject.ts"],"names":[],"mappings":"AAEA,OAAO,EAA8B,IAAI,EAAC,MAAM,kBAAkB,CAAA;AAClE,OAAO,EAAsB,cAAc,EAAE,aAAa,EAAC,MAAM,yBAAyB,CAAA;AAC1F,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAGlC,yBAAiB,UAAU,CAAC;IACxB,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,gBAAgB;QAC7B,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,gBAAgB,CAAA;KAC9B,CAwBA,CAAA;IAEM,MAAM,MAAM,GAAI,SAAS,OAAO,EAAE,UAAU,cAAc,KAAG,OAAO,CAAC,WAAW,CAgBtF,CAAA;CACJ"}
@@ -0,0 +1,48 @@
1
+ import JSZip from "jszip";
2
+ import { Xml } from "@opendaw/lib-xml";
3
+ import { asDefined, isDefined, panic, UUID } from "@opendaw/lib-std";
4
+ import { FileReferenceSchema, MetaDataSchema, ProjectSchema } from "@opendaw/lib-dawproject";
5
+ import { DawProjectExporter } from "./DawProjectExporter";
6
+ export var DawProject;
7
+ (function (DawProject) {
8
+ DawProject.decode = async (buffer) => {
9
+ const zip = await JSZip.loadAsync(buffer);
10
+ const metaDataXml = await zip.file("metadata.xml")?.async("string");
11
+ const metaData = isDefined(metaDataXml) ? Xml.parse(metaDataXml, MetaDataSchema) : Xml.element({}, MetaDataSchema);
12
+ const projectXml = asDefined(await zip.file("project.xml")?.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
+ DawProject.encode = (project, metaData) => {
32
+ const zip = new JSZip();
33
+ const projectSchema = DawProjectExporter.write(project, {
34
+ write: (path, buffer) => {
35
+ zip.file(path, buffer);
36
+ return Xml.element({ path, external: false }, FileReferenceSchema);
37
+ }
38
+ });
39
+ const metaDataXml = Xml.pretty(Xml.toElement("MetaData", metaData));
40
+ const projectXml = Xml.pretty(Xml.toElement("Project", projectSchema));
41
+ console.debug("encode");
42
+ console.debug(metaDataXml);
43
+ console.debug(projectXml);
44
+ zip.file("metadata.xml", metaDataXml);
45
+ zip.file("project.xml", projectXml);
46
+ return zip.generateAsync({ type: "arraybuffer" });
47
+ };
48
+ })(DawProject || (DawProject = {}));
@@ -1,8 +1,9 @@
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;
1
+ import { FileReferenceSchema, ProjectSchema } from "@opendaw/lib-dawproject";
2
+ import { Project } from "../Project";
3
+ export declare namespace DawProjectExporter {
4
+ interface ResourcePacker {
5
+ write(path: string, buffer: ArrayBufferLike): FileReferenceSchema;
6
+ }
7
+ const write: (project: Project, resourcePacker: ResourcePacker) => ProjectSchema;
7
8
  }
8
9
  //# sourceMappingURL=DawProjectExporter.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"DawProjectExporter.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectExporter.ts"],"names":[],"mappings":"AAGA,OAAO,EAaH,mBAAmB,EAKnB,aAAa,EAUhB,MAAM,yBAAyB,CAAA;AAchC,OAAO,EAAC,OAAO,EAAC,MAAM,YAAY,CAAA;AAQlC,yBAAiB,kBAAkB,CAAC;IAChC,UAAiB,cAAc;QAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,mBAAmB,CAAA;KAAC;IAE5F,MAAM,KAAK,GAAI,SAAS,OAAO,EAAE,gBAAgB,cAAc,kBA4PrE,CAAA;CACJ"}
@@ -1,29 +1,252 @@
1
+ import { asDefined, asInstanceOf, Color, ifDefined, isInstanceOf, Option, UUID } from "@opendaw/lib-std";
1
2
  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({
3
+ import { dbToGain, PPQN } from "@opendaw/lib-dsp";
4
+ import { ApplicationSchema, ArrangementSchema, AudioAlgorithm, AudioSchema, BuiltinDeviceSchema, ChannelRole, ChannelSchema, ClipSchema, ClipsSchema, DeviceRole, FileReferenceSchema, LanesSchema, NoteSchema, NotesSchema, ParameterEncoder, ProjectSchema, RealParameterSchema, TimeSignatureParameterSchema, TimeUnit, TrackSchema, TransportSchema, Unit, WarpSchema, WarpsSchema } from "@opendaw/lib-dawproject";
5
+ import { AddressIdEncoder, BooleanField } from "@opendaw/lib-box";
6
+ import { AudioUnitType } from "@opendaw/studio-enums";
7
+ import { AudioFileBox, AudioUnitBox, NoteEventBox, NoteEventCollectionBox, TrackBox } from "@opendaw/studio-boxes";
8
+ import { AudioUnitExportLayout } from "./AudioUnitExportLayout";
9
+ import { ColorCodes } from "../ColorCodes";
10
+ import { Html } from "@opendaw/lib-dom";
11
+ import { encodeWavFloat } from "../Wav";
12
+ import { DeviceBoxUtils } from "@opendaw/studio-adapters";
13
+ import { DeviceIO } from "./DeviceIO";
14
+ export var DawProjectExporter;
15
+ (function (DawProjectExporter) {
16
+ DawProjectExporter.write = (project, resourcePacker) => {
17
+ const ids = new AddressIdEncoder();
18
+ const { boxGraph, timelineBox, rootBox, sampleManager } = project;
19
+ const audioUnits = rootBox.audioUnits.pointerHub.incoming()
20
+ .map(({ box }) => asInstanceOf(box, AudioUnitBox))
21
+ .sort((a, b) => a.index.getValue() - b.index.getValue());
22
+ boxGraph.boxes().forEach(box => box.accept({
23
+ visitAudioFileBox: (box) => sampleManager.getOrCreate(box.address.uuid).data
24
+ .ifSome(({ frames, numberOfFrames, sampleRate, numberOfChannels }) => resourcePacker.write(`samples/${box.fileName.getValue()}.wav`, encodeWavFloat({
25
+ channels: frames,
26
+ duration: numberOfFrames * sampleRate,
27
+ numberOfChannels,
28
+ sampleRate,
29
+ numFrames: numberOfFrames
30
+ })))
31
+ }));
32
+ const writeTransport = () => Xml.element({
33
+ tempo: Xml.element({
34
+ id: ids.getOrCreate(timelineBox.bpm.address),
35
+ value: timelineBox.bpm.getValue(),
36
+ unit: Unit.BPM
37
+ }, RealParameterSchema),
38
+ timeSignature: Xml.element({
39
+ numerator: timelineBox.signature.nominator.getValue(),
40
+ denominator: timelineBox.signature.denominator.getValue()
41
+ }, TimeSignatureParameterSchema)
42
+ }, TransportSchema);
43
+ const writeDevices = (field, deviceRole) => field.pointerHub
44
+ .incoming().map(({ box }) => {
45
+ const enabled = ("enabled" in box && isInstanceOf(box.enabled, BooleanField)
46
+ ? Option.wrap(box.enabled)
47
+ : Option.None)
48
+ .mapOr(field => ParameterEncoder.bool(ids.getOrCreate(field.address), field.getValue(), "On/Off"), undefined);
49
+ const deviceID = box.name;
50
+ const deviceName = DeviceBoxUtils.lookupLabelField(box).getValue();
51
+ const deviceVendor = "openDAW";
52
+ const id = ids.getOrCreate(box.address);
53
+ return Xml.element({
54
+ id,
55
+ deviceID,
56
+ deviceRole,
57
+ deviceName,
58
+ deviceVendor,
59
+ enabled,
60
+ loaded: true,
61
+ automatedParameters: [],
62
+ state: resourcePacker
63
+ .write(`presets/${UUID.toString(box.address.uuid)}`, DeviceIO.exportDevice(box))
64
+ }, BuiltinDeviceSchema);
65
+ });
66
+ const colorForAudioType = (unitType) => {
67
+ const cssColor = ColorCodes.forAudioType(unitType);
68
+ if (cssColor === "") {
69
+ return "red";
70
+ }
71
+ const [r, g, b] = Html.readCssVarColor(ColorCodes.forAudioType(unitType))[0];
72
+ const RR = Math.round(r * 255).toString(16).padStart(2, "0");
73
+ const GG = Math.round(g * 255).toString(16).padStart(2, "0");
74
+ const BB = Math.round(b * 255).toString(16).padStart(2, "0");
75
+ return `#${RR}${GG}${BB}`;
76
+ };
77
+ const writeStructure = () => {
78
+ const tracks = AudioUnitExportLayout.layout(audioUnits);
79
+ const writeAudioUnitBox = (audioUnitBox, tracks) => {
80
+ const unitType = audioUnitBox.type.getValue();
81
+ const color = colorForAudioType(unitType);
82
+ const isPrimary = unitType === AudioUnitType.Output;
83
+ const isAux = unitType === AudioUnitType.Aux;
84
+ const inputBox = audioUnitBox.input.pointerHub.incoming().at(0)?.box;
85
+ const contentType = isPrimary ? "audio notes" // we copied that value from bitwig
86
+ : isAux ? "audio" : (() =>
87
+ // TODO Another location to remember to put devices in...?
88
+ // We could also read the first track type?
89
+ inputBox?.accept({
90
+ visitTapeDeviceBox: () => "audio",
91
+ visitVaporisateurDeviceBox: () => "notes",
92
+ visitNanoDeviceBox: () => "notes",
93
+ visitPlayfieldDeviceBox: () => "notes",
94
+ visitAudioBusBox: () => "tracks"
95
+ }))() ?? undefined;
96
+ return Xml.element({
97
+ id: ids.getOrCreate(audioUnitBox.address),
98
+ name: DeviceBoxUtils.lookupLabelField(inputBox).getValue(),
99
+ loaded: true,
100
+ color,
101
+ contentType,
102
+ channel: Xml.element({
103
+ id: ifDefined(inputBox, ({ address }) => ids.getOrCreate(address)),
104
+ destination: isPrimary
105
+ ? undefined
106
+ : audioUnitBox.output.targetVertex
107
+ .mapOr(({ box }) => ids.getOrCreate(box.address), undefined),
108
+ role: (() => {
109
+ switch (unitType) {
110
+ case AudioUnitType.Instrument:
111
+ return ChannelRole.REGULAR;
112
+ case AudioUnitType.Aux:
113
+ return ChannelRole.EFFECT;
114
+ case AudioUnitType.Bus:
115
+ case AudioUnitType.Output:
116
+ return ChannelRole.MASTER;
117
+ }
118
+ })(),
119
+ devices: [
120
+ ...(writeDevices(audioUnitBox.midiEffects, DeviceRole.NOTE_FX)),
121
+ ...(writeDevices(audioUnitBox.input, DeviceRole.INSTRUMENT)),
122
+ ...(writeDevices(audioUnitBox.audioEffects, DeviceRole.AUDIO_FX))
123
+ ],
124
+ volume: ParameterEncoder.linear(ids.getOrCreate(audioUnitBox.volume.address), dbToGain(audioUnitBox.volume.getValue()), 0.0, 2.0, "Volume"),
125
+ pan: ParameterEncoder.normalized(ids.getOrCreate(audioUnitBox.panning.address), (audioUnitBox.panning.getValue() + 1.0) / 2.0, 0.0, 1.0, "Pan")
126
+ }, ChannelSchema),
127
+ tracks
128
+ }, TrackSchema);
129
+ };
130
+ const writeTracks = (tracks) => tracks.map((track) => writeAudioUnitBox(track.audioUnit, writeTracks(track.children)));
131
+ return writeTracks(tracks);
132
+ };
133
+ const writeAudioRegion = (region) => {
134
+ const audioFileBox = asInstanceOf(region.file.targetVertex.unwrap("No file at region").box, AudioFileBox);
135
+ const audioElement = sampleManager.getOrCreate(audioFileBox.address.uuid).data
136
+ .map(({ numberOfFrames, sampleRate, numberOfChannels }) => Xml.element({
137
+ duration: numberOfFrames / sampleRate,
138
+ channels: numberOfChannels,
139
+ sampleRate,
140
+ algorithm: AudioAlgorithm.REPITCH,
141
+ file: Xml.element({
142
+ path: `samples/${audioFileBox.fileName.getValue()}.wav`,
143
+ external: false
144
+ }, FileReferenceSchema)
145
+ }, AudioSchema));
146
+ const duration = region.duration.getValue() / PPQN.Quarter;
147
+ return Xml.element({
148
+ clips: [Xml.element({
149
+ time: region.position.getValue() / PPQN.Quarter,
150
+ duration,
151
+ contentTimeUnit: TimeUnit.BEATS,
152
+ playStart: 0.0,
153
+ loopStart: 0.0,
154
+ loopEnd: region.loopDuration.getValue() / PPQN.Quarter,
155
+ enable: !region.mute.getValue(),
156
+ name: region.label.getValue(),
157
+ color: Color.hslToHex(region.hue.getValue(), 1.0, 0.60),
158
+ content: [Xml.element({
159
+ content: audioElement.mapOr(element => [element], []),
160
+ contentTimeUnit: "beats",
161
+ warps: [
162
+ Xml.element({
163
+ time: 0.0,
164
+ contentTime: 0.0
165
+ }, WarpSchema),
166
+ Xml.element({
167
+ time: duration,
168
+ contentTime: audioElement.mapOr(element => element.duration, 0)
169
+ }, WarpSchema)
170
+ ]
171
+ }, WarpsSchema)]
172
+ }, ClipSchema)]
173
+ }, ClipsSchema);
174
+ };
175
+ const writeNoteRegion = (region) => {
176
+ const collectionBox = asInstanceOf(region.events.targetVertex
177
+ .unwrap("No notes in region").box, NoteEventCollectionBox);
178
+ return Xml.element({
179
+ clips: [Xml.element({
180
+ time: region.position.getValue() / PPQN.Quarter,
181
+ duration: region.duration.getValue() / PPQN.Quarter,
182
+ contentTimeUnit: TimeUnit.BEATS,
183
+ playStart: 0.0,
184
+ loopStart: 0.0,
185
+ loopEnd: region.loopDuration.getValue() / PPQN.Quarter,
186
+ enable: !region.mute.getValue(),
187
+ name: region.label.getValue(),
188
+ color: Color.hslToHex(region.hue.getValue(), 1.0, 0.60),
189
+ content: [Xml.element({
190
+ notes: collectionBox.events.pointerHub.incoming()
191
+ .map(({ box }) => asInstanceOf(box, NoteEventBox))
192
+ .map(box => Xml.element({
193
+ time: box.position.getValue() / PPQN.Quarter,
194
+ duration: box.duration.getValue() / PPQN.Quarter,
195
+ key: box.pitch.getValue(),
196
+ channel: 0,
197
+ vel: box.velocity.getValue(),
198
+ rel: box.velocity.getValue()
199
+ }, NoteSchema))
200
+ }, NotesSchema)]
201
+ }, ClipSchema)]
202
+ }, ClipsSchema);
203
+ };
204
+ // TODO Implement!
205
+ const writeValueRegion = (region) => Xml.element({
206
+ clips: [Xml.element({
207
+ time: region.position.getValue() / PPQN.Quarter,
208
+ duration: region.duration.getValue() / PPQN.Quarter,
209
+ contentTimeUnit: TimeUnit.BEATS,
210
+ playStart: 0.0,
211
+ loopStart: 0.0,
212
+ loopEnd: region.loopDuration.getValue() / PPQN.Quarter,
213
+ enable: !region.mute.getValue(),
214
+ name: region.label.getValue(),
215
+ color: Color.hslToHex(region.hue.getValue(), 1.0, 0.60),
216
+ content: []
217
+ }, ClipSchema)]
218
+ }, ClipsSchema);
219
+ const writeLanes = () => {
220
+ return audioUnits
221
+ .flatMap(audioUnitBox => audioUnitBox.tracks.pointerHub.incoming()
222
+ .map(({ box }) => asInstanceOf(box, TrackBox))
223
+ .sort((a, b) => a.index.getValue() - b.index.getValue())
224
+ .map(trackBox => Xml.element({
225
+ id: ids.getOrCreate(trackBox.address),
226
+ track: ids.getOrCreate(audioUnitBox.address),
227
+ lanes: trackBox.regions.pointerHub.incoming()
228
+ .map(({ box }) => asDefined(box.accept({
229
+ visitAudioRegionBox: (region) => writeAudioRegion(region),
230
+ visitNoteRegionBox: (region) => writeNoteRegion(region),
231
+ visitValueRegionBox: (region) => writeValueRegion(region)
232
+ }), "Could not write region."))
233
+ }, LanesSchema)));
234
+ };
235
+ return Xml.element({
12
236
  version: "1.0",
13
237
  application: Xml.element({
14
238
  name: "openDAW",
15
239
  version: "0.1"
16
240
  }, 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),
241
+ transport: writeTransport(),
242
+ structure: writeStructure(),
243
+ arrangement: Xml.element({
244
+ lanes: Xml.element({
245
+ lanes: writeLanes(),
246
+ timeUnit: TimeUnit.BEATS
247
+ }, LanesSchema)
248
+ }, ArrangementSchema),
25
249
  scenes: []
26
- }, ProjectSchema));
27
- return Xml.pretty(element);
28
- }
29
- }
250
+ }, ProjectSchema);
251
+ };
252
+ })(DawProjectExporter || (DawProjectExporter = {}));
@@ -2,15 +2,47 @@ import { describe, it } from "vitest";
2
2
  import { fileURLToPath } from "url";
3
3
  import * as path from "node:path";
4
4
  import * as fs from "node:fs";
5
- import { ProjectDecoder } from "@opendaw/studio-adapters";
6
- import { ProjectMigration } from "../ProjectMigration";
5
+ import { Project } from "../Project";
6
+ import { Option, panic, Terminable } from "@opendaw/lib-std";
7
+ import { Xml } from "@opendaw/lib-xml";
8
+ import { FileReferenceSchema } from "@opendaw/lib-dawproject";
7
9
  import { DawProjectExporter } from "./DawProjectExporter";
8
10
  describe("DawProjectExport", () => {
9
11
  it("export", async () => {
10
12
  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());
13
+ const projectPath = "../../../../../test-files/all-devices.od";
14
+ const buffer = fs.readFileSync(path.join(__dirname, projectPath));
15
+ const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
16
+ const project = Project.load({
17
+ sampleManager: new class {
18
+ record(_loader) {
19
+ throw new Error("Method not implemented.");
20
+ }
21
+ getOrCreate(format) {
22
+ return new class {
23
+ data = Option.None;
24
+ peaks = Option.None;
25
+ uuid = format;
26
+ state = { type: "progress", progress: 0.0 };
27
+ meta = Option.None;
28
+ invalidate() { throw new Error("Method not implemented."); }
29
+ subscribe(_observer) {
30
+ return Terminable.Empty;
31
+ }
32
+ };
33
+ }
34
+ invalidate(_uuid) {
35
+ return panic("Method not implemented.");
36
+ }
37
+ }
38
+ }, arrayBuffer);
39
+ const schema = DawProjectExporter.write(project, {
40
+ write: (path, buffer) => {
41
+ console.debug(`store ${buffer.byteLength} bytes at ${path}`);
42
+ return Xml.element({ path, external: false }, FileReferenceSchema);
43
+ }
44
+ });
45
+ // console.dir(schema, {depth: Number.MAX_SAFE_INTEGER})
46
+ console.debug(Xml.pretty(Xml.toElement("Project", schema)));
15
47
  });
16
48
  });
@@ -0,0 +1,12 @@
1
+ import { UUID } from "@opendaw/lib-std";
2
+ import { ProjectSchema } from "@opendaw/lib-dawproject";
3
+ import { ProjectDecoder } from "@opendaw/studio-adapters";
4
+ import { DawProject } from "./DawProject";
5
+ export declare namespace DawProjectImport {
6
+ type Result = {
7
+ audioIds: ReadonlyArray<UUID.Format>;
8
+ skeleton: ProjectDecoder.Skeleton;
9
+ };
10
+ const read: (schema: ProjectSchema, resources: DawProject.ResourceProvider) => Promise<Result>;
11
+ }
12
+ //# sourceMappingURL=DawProjectImport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DawProjectImport.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectImport.ts"],"names":[],"mappings":"AAAA,OAAO,EAgBH,IAAI,EAEP,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EAcH,aAAa,EAOhB,MAAM,yBAAyB,CAAA;AAuBhC,OAAO,EAAyC,cAAc,EAAY,MAAM,0BAA0B,CAAA;AAC1G,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAA;AAQvC,yBAAiB,gBAAgB,CAAC;IAW9B,KAAY,MAAM,GAAG;QACjB,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrC,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAA;KACpC,CAAA;IAOM,MAAM,IAAI,GAAU,QAAQ,aAAa,EAAE,WAAW,UAAU,CAAC,gBAAgB,KAAG,OAAO,CAAC,MAAM,CA6WxG,CAAA;CAaJ"}