@opendaw/studio-core 0.0.18 → 0.0.20
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/AudioInputDevices.d.ts +8 -0
- package/dist/AudioInputDevices.d.ts.map +1 -0
- package/dist/AudioInputDevices.js +30 -0
- package/dist/AudioUnitOrdering.d.ts +3 -0
- package/dist/AudioUnitOrdering.d.ts.map +1 -0
- package/dist/AudioUnitOrdering.js +7 -0
- package/dist/EffectBox.d.ts +2 -2
- package/dist/EffectBox.d.ts.map +1 -1
- package/dist/Engine.d.ts +31 -10
- package/dist/Engine.d.ts.map +1 -1
- package/dist/EngineFacade.d.ts +22 -11
- package/dist/EngineFacade.d.ts.map +1 -1
- package/dist/EngineFacade.js +39 -22
- package/dist/EngineWorklet.d.ts +22 -13
- package/dist/EngineWorklet.d.ts.map +1 -1
- package/dist/EngineWorklet.js +47 -56
- package/dist/MeterWorklet.d.ts.map +1 -1
- package/dist/MeterWorklet.js +2 -2
- package/dist/Project.d.ts +7 -3
- package/dist/Project.d.ts.map +1 -1
- package/dist/Project.js +22 -4
- package/dist/ProjectApi.d.ts +0 -1
- package/dist/ProjectApi.d.ts.map +1 -1
- package/dist/ProjectApi.js +16 -11
- package/dist/ProjectEnv.d.ts +1 -0
- package/dist/ProjectEnv.d.ts.map +1 -1
- package/dist/ProjectMigration.d.ts.map +1 -1
- package/dist/ProjectMigration.js +20 -3
- package/dist/RecordingWorklet.d.ts +15 -3
- package/dist/RecordingWorklet.d.ts.map +1 -1
- package/dist/RecordingWorklet.js +111 -16
- package/dist/WorkerAgents.d.ts +2 -2
- package/dist/WorkerAgents.d.ts.map +1 -1
- package/dist/Worklets.d.ts +1 -1
- package/dist/Worklets.d.ts.map +1 -1
- package/dist/Worklets.js +2 -2
- package/dist/capture/Capture.d.ts +22 -0
- package/dist/capture/Capture.d.ts.map +1 -0
- package/dist/capture/Capture.js +32 -0
- package/dist/capture/CaptureAudio.d.ts +17 -0
- package/dist/capture/CaptureAudio.d.ts.map +1 -0
- package/dist/capture/CaptureAudio.js +106 -0
- package/dist/capture/CaptureManager.d.ts +12 -0
- package/dist/capture/CaptureManager.d.ts.map +1 -0
- package/dist/capture/CaptureManager.js +38 -0
- package/dist/capture/CaptureMidi.d.ts +13 -0
- package/dist/capture/CaptureMidi.d.ts.map +1 -0
- package/dist/capture/CaptureMidi.js +50 -0
- package/dist/capture/RecordAudio.d.ts +21 -0
- package/dist/capture/RecordAudio.d.ts.map +1 -0
- package/dist/capture/RecordAudio.js +66 -0
- package/dist/capture/RecordMidi.d.ts +15 -0
- package/dist/capture/RecordMidi.d.ts.map +1 -0
- package/dist/capture/RecordMidi.js +85 -0
- package/dist/capture/RecordTrack.d.ts +7 -0
- package/dist/capture/RecordTrack.d.ts.map +1 -0
- package/dist/capture/RecordTrack.js +23 -0
- package/dist/capture/Recording.d.ts +9 -0
- package/dist/capture/Recording.d.ts.map +1 -0
- package/dist/capture/Recording.js +65 -0
- package/dist/capture/RecordingContext.d.ts +14 -0
- package/dist/capture/RecordingContext.d.ts.map +1 -0
- package/dist/capture/RecordingContext.js +1 -0
- package/dist/dawproject/AudioUnitExportLayout.d.ts +10 -0
- package/dist/dawproject/AudioUnitExportLayout.d.ts.map +1 -0
- package/dist/dawproject/AudioUnitExportLayout.js +64 -0
- package/dist/dawproject/BuiltinDevices.d.ts +9 -0
- package/dist/dawproject/BuiltinDevices.d.ts.map +1 -0
- package/dist/dawproject/BuiltinDevices.js +70 -0
- package/dist/dawproject/DawProject.d.ts +22 -0
- package/dist/dawproject/DawProject.d.ts.map +1 -0
- package/dist/dawproject/DawProject.js +49 -0
- package/dist/dawproject/DawProjectExporter.d.ts +9 -0
- package/dist/dawproject/DawProjectExporter.d.ts.map +1 -0
- package/dist/dawproject/DawProjectExporter.js +252 -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 +49 -0
- package/dist/dawproject/DawProjectImport.d.ts +12 -0
- package/dist/dawproject/DawProjectImport.d.ts.map +1 -0
- package/dist/dawproject/DawProjectImport.js +389 -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 +15 -0
- package/dist/dawproject/DeviceIO.d.ts +8 -0
- package/dist/dawproject/DeviceIO.d.ts.map +1 -0
- package/dist/dawproject/DeviceIO.js +63 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/processors.js +3 -3
- package/dist/processors.js.map +4 -4
- package/dist/samples/MainThreadSampleLoader.d.ts +1 -0
- package/dist/samples/MainThreadSampleLoader.d.ts.map +1 -1
- package/dist/samples/MainThreadSampleLoader.js +10 -6
- package/dist/samples/MainThreadSampleManager.d.ts +5 -5
- package/dist/samples/MainThreadSampleManager.d.ts.map +1 -1
- package/dist/samples/MainThreadSampleManager.js +1 -0
- package/dist/samples/SampleProvider.d.ts +2 -2
- package/dist/samples/SampleProvider.d.ts.map +1 -1
- package/dist/samples/SampleStorage.d.ts.map +1 -1
- package/dist/samples/SampleStorage.js +2 -3
- package/dist/workers.js +2 -2
- package/dist/workers.js.map +4 -4
- package/package.json +17 -17
- package/dist/DawProjectIO.d.ts +0 -7
- package/dist/DawProjectIO.d.ts.map +0 -1
- package/dist/DawProjectIO.js +0 -73
- package/dist/samples/SamplePeaks.d.ts +0 -6
- package/dist/samples/SamplePeaks.d.ts.map +0 -1
- package/dist/samples/SamplePeaks.js +0 -9
@@ -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 = {}));
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { UUID } from "@opendaw/lib-std";
|
2
|
+
import { MetaDataSchema, ProjectSchema } from "@opendaw/lib-dawproject";
|
3
|
+
import { Project } from "../Project";
|
4
|
+
export declare namespace DawProject {
|
5
|
+
type Resource = {
|
6
|
+
uuid: UUID.Format;
|
7
|
+
path: string;
|
8
|
+
name: string;
|
9
|
+
buffer: ArrayBuffer;
|
10
|
+
};
|
11
|
+
interface ResourceProvider {
|
12
|
+
fromPath(path: string): Resource;
|
13
|
+
fromUUID(uuid: UUID.Format): Resource;
|
14
|
+
}
|
15
|
+
const decode: (buffer: ArrayBuffer | NonSharedBuffer) => Promise<{
|
16
|
+
metaData: MetaDataSchema;
|
17
|
+
project: ProjectSchema;
|
18
|
+
resources: ResourceProvider;
|
19
|
+
}>;
|
20
|
+
const encode: (project: Project, metaData: MetaDataSchema) => Promise<ArrayBuffer>;
|
21
|
+
}
|
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,EAAmB,IAAI,EAAC,MAAM,kBAAkB,CAAA;AACvD,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,CAyBA,CAAA;IAEM,MAAM,MAAM,GAAI,SAAS,OAAO,EAAE,UAAU,cAAc,KAAG,OAAO,CAAC,WAAW,CAgBtF,CAAA;CACJ"}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import JSZip from "jszip";
|
2
|
+
import { Xml } from "@opendaw/lib-xml";
|
3
|
+
import { asDefined, 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 metaData = Xml.parse(asDefined(await zip.file("metadata.xml")
|
11
|
+
?.async("string"), "No metadata.xml found"), MetaDataSchema);
|
12
|
+
const projectXml = asDefined(await zip.file("project.xml")
|
13
|
+
?.async("string"), "No project.xml found");
|
14
|
+
console.debug(projectXml);
|
15
|
+
const project = Xml.parse(projectXml, ProjectSchema);
|
16
|
+
const resourceFiles = Object.entries(zip.files).filter(([_, file]) => !file.dir && !file.name.endsWith(".xml"));
|
17
|
+
const resources = await Promise.all(resourceFiles.map(async ([path, file]) => {
|
18
|
+
const name = path.substring(path.lastIndexOf("/") + 1);
|
19
|
+
const buffer = await file.async("arraybuffer");
|
20
|
+
const uuid = await UUID.sha256(new Uint8Array(buffer).buffer);
|
21
|
+
return { uuid, path, name, buffer };
|
22
|
+
}));
|
23
|
+
return {
|
24
|
+
metaData, project, resources: {
|
25
|
+
fromPath: (path) => resources
|
26
|
+
.find(resource => resource.path === path) ?? panic("Resource not found"),
|
27
|
+
fromUUID: (uuid) => resources
|
28
|
+
.find(resource => UUID.equals(resource.uuid, uuid)) ?? panic("Resource not found")
|
29
|
+
}
|
30
|
+
};
|
31
|
+
};
|
32
|
+
DawProject.encode = (project, metaData) => {
|
33
|
+
const zip = new JSZip();
|
34
|
+
const projectSchema = DawProjectExporter.write(project, {
|
35
|
+
write: (path, buffer) => {
|
36
|
+
zip.file(path, buffer);
|
37
|
+
return Xml.element({ path, external: false }, FileReferenceSchema);
|
38
|
+
}
|
39
|
+
});
|
40
|
+
const metaDataXml = Xml.pretty(Xml.toElement("MetaData", metaData));
|
41
|
+
const projectXml = Xml.pretty(Xml.toElement("Project", projectSchema));
|
42
|
+
console.debug("encode");
|
43
|
+
console.debug(metaDataXml);
|
44
|
+
console.debug(projectXml);
|
45
|
+
zip.file("metadata.xml", metaDataXml);
|
46
|
+
zip.file("project.xml", projectXml);
|
47
|
+
return zip.generateAsync({ type: "arraybuffer" });
|
48
|
+
};
|
49
|
+
})(DawProject || (DawProject = {}));
|
@@ -0,0 +1,9 @@
|
|
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;
|
8
|
+
}
|
9
|
+
//# sourceMappingURL=DawProjectExporter.d.ts.map
|
@@ -0,0 +1 @@
|
|
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"}
|
@@ -0,0 +1,252 @@
|
|
1
|
+
import { asDefined, asInstanceOf, Color, ifDefined, isInstanceOf, Option, UUID } from "@opendaw/lib-std";
|
2
|
+
import { Xml } from "@opendaw/lib-xml";
|
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({
|
236
|
+
version: "1.0",
|
237
|
+
application: Xml.element({
|
238
|
+
name: "openDAW",
|
239
|
+
version: "0.1"
|
240
|
+
}, ApplicationSchema),
|
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),
|
249
|
+
scenes: []
|
250
|
+
}, ProjectSchema);
|
251
|
+
};
|
252
|
+
})(DawProjectExporter || (DawProjectExporter = {}));
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"DawProjectExporter.test.d.ts","sourceRoot":"","sources":["../../src/dawproject/DawProjectExporter.test.ts"],"names":[],"mappings":""}
|
@@ -0,0 +1,49 @@
|
|
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 { 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";
|
9
|
+
import { DawProjectExporter } from "./DawProjectExporter";
|
10
|
+
describe("DawProjectExport", () => {
|
11
|
+
it("export", async () => {
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
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
|
+
sampleRate: 44100,
|
18
|
+
sampleManager: new class {
|
19
|
+
record(loader) {
|
20
|
+
throw new Error("Method not implemented.");
|
21
|
+
}
|
22
|
+
getOrCreate(format) {
|
23
|
+
return new class {
|
24
|
+
data = Option.None;
|
25
|
+
peaks = Option.None;
|
26
|
+
uuid = format;
|
27
|
+
state = { type: "progress", progress: 0.0 };
|
28
|
+
meta = Option.None;
|
29
|
+
invalidate() { throw new Error("Method not implemented."); }
|
30
|
+
subscribe(_observer) {
|
31
|
+
return Terminable.Empty;
|
32
|
+
}
|
33
|
+
};
|
34
|
+
}
|
35
|
+
invalidate(_uuid) {
|
36
|
+
return panic("Method not implemented.");
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}, arrayBuffer);
|
40
|
+
const schema = DawProjectExporter.write(project, {
|
41
|
+
write: (path, buffer) => {
|
42
|
+
console.debug(`store ${buffer.byteLength} bytes at ${path}`);
|
43
|
+
return Xml.element({ path, external: false }, FileReferenceSchema);
|
44
|
+
}
|
45
|
+
});
|
46
|
+
// console.dir(schema, {depth: Number.MAX_SAFE_INTEGER})
|
47
|
+
console.debug(Xml.pretty(Xml.toElement("Project", schema)));
|
48
|
+
});
|
49
|
+
});
|
@@ -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"}
|