@opendaw/studio-core 0.0.19 → 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 +4 -2
- package/dist/Project.d.ts.map +1 -1
- package/dist/Project.js +6 -3
- 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/{DawProjectIO.d.ts → DawProject.d.ts} +6 -4
- package/dist/dawproject/DawProject.d.ts.map +1 -0
- package/dist/dawproject/{DawProjectIO.js → DawProject.js} +23 -5
- package/dist/dawproject/DawProjectExporter.d.ts +7 -6
- package/dist/dawproject/DawProjectExporter.d.ts.map +1 -1
- package/dist/dawproject/DawProjectExporter.js +245 -22
- package/dist/dawproject/DawProjectExporter.test.js +39 -6
- 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.js +6 -7
- 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 +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -3
- 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 +15 -15
- package/dist/dawproject/DawProjectIO.d.ts.map +0 -1
- package/dist/dawproject/DawProjectImporter.d.ts +0 -12
- package/dist/dawproject/DawProjectImporter.d.ts.map +0 -1
- package/dist/dawproject/DawProjectImporter.js +0 -273
- 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,389 @@
|
|
1
|
+
import { ArrayMultimap, asDefined, asInstanceOf, assert, Color, ifDefined, isDefined, isInstanceOf, isUndefined, NumberComparator, 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 { ChannelRole, ClipsSchema, DeviceRole, EqualizerSchema, LanesSchema, NotesSchema, PointsSchema, SendType, TrackSchema, WarpsSchema } from "@opendaw/lib-dawproject";
|
5
|
+
import { AudioSendRouting, AudioUnitType } from "@opendaw/studio-enums";
|
6
|
+
import { AudioBusBox, AudioFileBox, AudioRegionBox, AudioUnitBox, AuxSendBox, BoxIO, CaptureAudioBox, CaptureMidiBox, GrooveShuffleBox, NoteEventBox, NoteEventCollectionBox, NoteRegionBox, RootBox, TimelineBox, TrackBox, UnknownAudioEffectDeviceBox, UnknownMidiEffectDeviceBox, UserInterfaceBox } from "@opendaw/studio-boxes";
|
7
|
+
import { DeviceBoxUtils, IconSymbol, TrackType } from "@opendaw/studio-adapters";
|
8
|
+
import { AudioUnitOrdering } from "../AudioUnitOrdering";
|
9
|
+
import { InstrumentFactories } from "../InstrumentFactories";
|
10
|
+
import { ColorCodes } from "../ColorCodes";
|
11
|
+
import { DeviceIO } from "./DeviceIO";
|
12
|
+
import { BuiltinDevices } from "./BuiltinDevices";
|
13
|
+
export var DawProjectImport;
|
14
|
+
(function (DawProjectImport) {
|
15
|
+
const readTransport = ({ tempo, timeSignature }, { bpm, signature: { nominator, denominator } }) => {
|
16
|
+
ifDefined(tempo?.value, value => bpm.setValue(value));
|
17
|
+
ifDefined(timeSignature?.numerator, value => nominator.setValue(value));
|
18
|
+
ifDefined(timeSignature?.denominator, value => denominator.setValue(value));
|
19
|
+
};
|
20
|
+
const toAudioUnitCapture = (boxGraph, contentType) => {
|
21
|
+
if (contentType === "audio")
|
22
|
+
return Option.wrap(CaptureAudioBox.create(boxGraph, UUID.generate()));
|
23
|
+
if (contentType === "notes")
|
24
|
+
return Option.wrap(CaptureMidiBox.create(boxGraph, UUID.generate()));
|
25
|
+
return Option.None;
|
26
|
+
};
|
27
|
+
DawProjectImport.read = async (schema, resources) => {
|
28
|
+
const boxGraph = new BoxGraph(Option.wrap(BoxIO.create));
|
29
|
+
boxGraph.beginTransaction();
|
30
|
+
// Create fundamental boxes
|
31
|
+
//
|
32
|
+
const isoString = new Date().toISOString();
|
33
|
+
console.debug(`New Project imported on ${isoString}`);
|
34
|
+
const grooveShuffleBox = GrooveShuffleBox.create(boxGraph, UUID.generate(), box => box.label.setValue("Groove Shuffle"));
|
35
|
+
const timelineBox = TimelineBox.create(boxGraph, UUID.generate(), box => ifDefined(schema.transport, transport => readTransport(transport, box)));
|
36
|
+
const rootBox = RootBox.create(boxGraph, UUID.generate(), box => {
|
37
|
+
box.groove.refer(grooveShuffleBox);
|
38
|
+
box.created.setValue(isoString);
|
39
|
+
box.timeline.refer(timelineBox.root);
|
40
|
+
});
|
41
|
+
const userInterfaceBox = UserInterfaceBox.create(boxGraph, UUID.generate(), box => box.root.refer(rootBox.users));
|
42
|
+
// <---
|
43
|
+
let primaryAudioBusUnitOption = Option.None;
|
44
|
+
const audioIdSet = UUID.newSet(uuid => uuid);
|
45
|
+
const audioUnits = new Map();
|
46
|
+
const registerAudioUnit = (trackId, target) => {
|
47
|
+
if (audioUnits.has(trackId)) {
|
48
|
+
return panic(`trackId '${trackId}' is already defined`);
|
49
|
+
}
|
50
|
+
audioUnits.set(trackId, target);
|
51
|
+
};
|
52
|
+
const audioBusses = new Map();
|
53
|
+
const registerAudioBus = (channelId, audioBusBox) => {
|
54
|
+
if (audioBusses.has(channelId)) {
|
55
|
+
return panic(`channelId '${channelId}' is already defined`);
|
56
|
+
}
|
57
|
+
audioBusses.set(channelId, audioBusBox);
|
58
|
+
};
|
59
|
+
const outputPointers = [];
|
60
|
+
const sortAudioUnits = new ArrayMultimap();
|
61
|
+
// Reading methods
|
62
|
+
//
|
63
|
+
const createEffect = (device, field, index) => {
|
64
|
+
const { deviceRole, deviceVendor, deviceID, deviceName, state } = device;
|
65
|
+
assert(deviceRole === DeviceRole.NOTE_FX || deviceRole === DeviceRole.AUDIO_FX, "Device is not an effect");
|
66
|
+
if (deviceVendor === "openDAW") {
|
67
|
+
console.debug(`Recreate openDAW effect device '${deviceName}' with id '${deviceID}'`);
|
68
|
+
const resource = ifDefined(state?.path, path => resources.fromPath(path));
|
69
|
+
if (isDefined(resource)) {
|
70
|
+
const device = DeviceIO.importDevice(boxGraph, resource.buffer);
|
71
|
+
device.host.refer(field);
|
72
|
+
device.label.setValue(deviceName ?? "");
|
73
|
+
DeviceBoxUtils.lookupIndexField(device).setValue(index);
|
74
|
+
return;
|
75
|
+
}
|
76
|
+
}
|
77
|
+
if (isInstanceOf(device, EqualizerSchema)) {
|
78
|
+
return BuiltinDevices.equalizer(boxGraph, device, field, index);
|
79
|
+
}
|
80
|
+
const comment = isDefined(deviceVendor) ? `${deviceID} from ${deviceVendor} ⚠️` : `${deviceID} ⚠️`;
|
81
|
+
switch (deviceRole) {
|
82
|
+
case DeviceRole.NOTE_FX:
|
83
|
+
return UnknownMidiEffectDeviceBox.create(boxGraph, UUID.generate(), box => {
|
84
|
+
box.host.refer(field);
|
85
|
+
box.index.setValue(index);
|
86
|
+
box.label.setValue(deviceName ?? "");
|
87
|
+
box.comment.setValue(comment);
|
88
|
+
});
|
89
|
+
case DeviceRole.AUDIO_FX:
|
90
|
+
return UnknownAudioEffectDeviceBox.create(boxGraph, UUID.generate(), box => {
|
91
|
+
box.host.refer(field);
|
92
|
+
box.index.setValue(index);
|
93
|
+
box.label.setValue(deviceName ?? "");
|
94
|
+
box.comment.setValue(comment);
|
95
|
+
});
|
96
|
+
}
|
97
|
+
};
|
98
|
+
const createSends = (audioUnitBox, sends) => {
|
99
|
+
sends
|
100
|
+
// bitwig does not set enabled if it is enabled 🤥
|
101
|
+
.filter((send) => isUndefined(send?.enable?.value) || send.enable.value)
|
102
|
+
.forEach((send, index) => {
|
103
|
+
const destination = asDefined(send.destination, "destination is undefined");
|
104
|
+
const auxSendBox = AuxSendBox.create(boxGraph, UUID.generate(), box => {
|
105
|
+
const type = send.type;
|
106
|
+
box.routing.setValue(type === SendType.PRE ? AudioSendRouting.Pre : AudioSendRouting.Post);
|
107
|
+
box.sendGain.setValue(gainToDb(send.volume?.value ?? 1.0)); // TODO Take unit into account
|
108
|
+
box.sendPan.setValue(ValueMapping.bipolar().y(send.pan?.value ?? 0.5)); // TODO Take unit into account
|
109
|
+
box.audioUnit.refer(audioUnitBox.auxSends);
|
110
|
+
box.index.setValue(index);
|
111
|
+
});
|
112
|
+
outputPointers.push({ target: destination, pointer: auxSendBox.targetBus });
|
113
|
+
return auxSendBox;
|
114
|
+
});
|
115
|
+
};
|
116
|
+
const createInstrumentBox = (audioUnitBox, track, device) => {
|
117
|
+
if (isDefined(device)) {
|
118
|
+
const { deviceName, deviceVendor, deviceID, state } = device;
|
119
|
+
if (deviceVendor === "openDAW") {
|
120
|
+
console.debug(`Recreate openDAW instrument device '${deviceName}' with id '${deviceID}'`);
|
121
|
+
const resource = ifDefined(state?.path, path => resources.fromPath(path));
|
122
|
+
if (isDefined(resource)) {
|
123
|
+
const device = DeviceIO.importDevice(boxGraph, resource.buffer);
|
124
|
+
device.host.refer(audioUnitBox.input);
|
125
|
+
device.label.setValue(deviceName ?? "");
|
126
|
+
assert(DeviceBoxUtils.isInstrumentDeviceBox(device), `${device.name} is not an instrument`);
|
127
|
+
return device;
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
131
|
+
if (track.contentType === "notes") {
|
132
|
+
return InstrumentFactories.Vaporisateur
|
133
|
+
.create(boxGraph, audioUnitBox.input, track.name ?? "", IconSymbol.Piano);
|
134
|
+
}
|
135
|
+
else if (track.contentType === "audio") {
|
136
|
+
return InstrumentFactories.Tape
|
137
|
+
.create(boxGraph, audioUnitBox.input, track.name ?? "", IconSymbol.Waveform);
|
138
|
+
}
|
139
|
+
return panic(`Cannot create instrument box for track '${track.name}' with contentType '${track.contentType}' and device '${device?.deviceName}'`);
|
140
|
+
};
|
141
|
+
const createAudioUnit = (channel, type, capture) => AudioUnitBox.create(rootBox.graph, UUID.generate(), box => {
|
142
|
+
box.collection.refer(rootBox.audioUnits);
|
143
|
+
box.type.setValue(type);
|
144
|
+
box.volume.setValue(gainToDb(channel.volume?.value ?? 1.0));
|
145
|
+
box.panning.setValue(ValueMapping.bipolar().y(channel.pan?.value ?? 0.5));
|
146
|
+
box.mute.setValue(channel.mute?.value === true);
|
147
|
+
box.solo.setValue(channel.solo === true);
|
148
|
+
capture.ifSome(capture => box.capture.refer(capture));
|
149
|
+
});
|
150
|
+
const createInstrumentUnit = (track, type, capture) => {
|
151
|
+
const channel = asDefined(track.channel);
|
152
|
+
const audioUnitBox = createAudioUnit(channel, type, capture);
|
153
|
+
sortAudioUnits.add(AudioUnitOrdering[type], audioUnitBox);
|
154
|
+
const instrumentDevice = ifDefined(channel.devices, devices => {
|
155
|
+
devices
|
156
|
+
.filter((device) => device.deviceRole === DeviceRole.NOTE_FX)
|
157
|
+
.forEach((device, index) => createEffect(device, audioUnitBox.midiEffects, index));
|
158
|
+
devices
|
159
|
+
.filter((device) => device.deviceRole === DeviceRole.AUDIO_FX)
|
160
|
+
.forEach((device, index) => createEffect(device, audioUnitBox.audioEffects, index));
|
161
|
+
return devices
|
162
|
+
.find((device) => device.deviceRole === DeviceRole.INSTRUMENT);
|
163
|
+
});
|
164
|
+
const instrumentBox = createInstrumentBox(audioUnitBox, track, instrumentDevice);
|
165
|
+
return { instrumentBox, audioUnitBox };
|
166
|
+
};
|
167
|
+
const createAudioBusUnit = (track, type, icon) => {
|
168
|
+
const channel = asDefined(track.channel);
|
169
|
+
const audioUnitBox = createAudioUnit(channel, type, Option.None);
|
170
|
+
sortAudioUnits.add(AudioUnitOrdering[type], audioUnitBox);
|
171
|
+
const audioBusBox = AudioBusBox.create(rootBox.graph, UUID.generate(), box => {
|
172
|
+
box.collection.refer(rootBox.audioBusses);
|
173
|
+
box.label.setValue(track.name ?? "");
|
174
|
+
box.icon.setValue(IconSymbol.toName(icon));
|
175
|
+
box.color.setValue(ColorCodes.forAudioType(type));
|
176
|
+
box.enabled.setValue(true);
|
177
|
+
box.output.refer(audioUnitBox.input);
|
178
|
+
});
|
179
|
+
ifDefined(channel.devices, devices => devices
|
180
|
+
.filter((device) => device.deviceRole === DeviceRole.AUDIO_FX)
|
181
|
+
.forEach((device, index) => createEffect(device, audioUnitBox.audioEffects, index)));
|
182
|
+
return { audioBusBox, audioUnitBox };
|
183
|
+
};
|
184
|
+
const readTracks = (lanes, depth) => lanes
|
185
|
+
.filter((lane) => isInstanceOf(lane, TrackSchema))
|
186
|
+
.forEach((track) => {
|
187
|
+
const channel = asDefined(track.channel, "Track has no Channel");
|
188
|
+
const channelId = asDefined(channel.id, "Channel must have an Id");
|
189
|
+
if (channel.role === ChannelRole.REGULAR) {
|
190
|
+
const { audioUnitBox } = createInstrumentUnit(track, AudioUnitType.Instrument, toAudioUnitCapture(boxGraph, track.contentType));
|
191
|
+
registerAudioUnit(asDefined(track.id, "Track must have an Id."), audioUnitBox);
|
192
|
+
ifDefined(channel.sends, sends => createSends(audioUnitBox, sends));
|
193
|
+
outputPointers.push({ target: channel.destination, pointer: audioUnitBox.output });
|
194
|
+
}
|
195
|
+
else if (channel.role === ChannelRole.EFFECT) {
|
196
|
+
const { audioBusBox, audioUnitBox } = createAudioBusUnit(track, AudioUnitType.Aux, IconSymbol.Effects);
|
197
|
+
registerAudioBus(channelId, audioBusBox);
|
198
|
+
registerAudioUnit(asDefined(track.id, "Track must have an Id."), audioUnitBox);
|
199
|
+
ifDefined(channel.sends, sends => createSends(audioUnitBox, sends));
|
200
|
+
outputPointers.push({ target: channel.destination, pointer: audioUnitBox.output });
|
201
|
+
}
|
202
|
+
else if (channel.role === ChannelRole.MASTER) {
|
203
|
+
const isMostLikelyPrimaryOutput = primaryAudioBusUnitOption.isEmpty()
|
204
|
+
&& depth === 0
|
205
|
+
&& isUndefined(channel.destination)
|
206
|
+
&& track.contentType !== "tracks";
|
207
|
+
if (isMostLikelyPrimaryOutput) {
|
208
|
+
const audioBusUnit = createAudioBusUnit(track, AudioUnitType.Output, IconSymbol.SpeakerHeadphone);
|
209
|
+
const { audioBusBox, audioUnitBox } = audioBusUnit;
|
210
|
+
registerAudioBus(channelId, audioBusBox);
|
211
|
+
registerAudioUnit(asDefined(track.id, "Track must have an Id."), audioUnitBox);
|
212
|
+
audioUnitBox.output.refer(rootBox.outputDevice);
|
213
|
+
primaryAudioBusUnitOption = Option.wrap(audioBusUnit);
|
214
|
+
}
|
215
|
+
else {
|
216
|
+
const audioBusUnit = createAudioBusUnit(track, AudioUnitType.Bus, IconSymbol.AudioBus);
|
217
|
+
const { audioBusBox, audioUnitBox } = audioBusUnit;
|
218
|
+
registerAudioBus(channelId, audioBusBox);
|
219
|
+
registerAudioUnit(asDefined(track.id, "Track must have an Id."), audioUnitBox);
|
220
|
+
ifDefined(channel.destination, destination => outputPointers.push({ target: destination, pointer: audioUnitBox.output }));
|
221
|
+
ifDefined(channel.sends, sends => createSends(audioUnitBox, sends));
|
222
|
+
readTracks(track.tracks ?? [], depth + 1);
|
223
|
+
}
|
224
|
+
}
|
225
|
+
else {
|
226
|
+
return panic(`Unknown channel role ('${channel.role}')`);
|
227
|
+
}
|
228
|
+
});
|
229
|
+
readTracks(schema.structure, 0);
|
230
|
+
const readArrangement = (arrangement) => {
|
231
|
+
const readTrackRegions = ({ clips }, trackId) => {
|
232
|
+
const audioUnitBox = asDefined(audioUnits.get(trackId), `Cannot find track for '${trackId}'`);
|
233
|
+
if (audioUnitBox.type.getValue() === AudioUnitType.Output) {
|
234
|
+
return Promise.resolve();
|
235
|
+
}
|
236
|
+
const inputTrackType = readInputTrackType(audioUnitBox);
|
237
|
+
const index = audioUnitBox.tracks.pointerHub.incoming().length;
|
238
|
+
const trackBox = TrackBox.create(boxGraph, UUID.generate(), box => {
|
239
|
+
box.tracks.refer(audioUnitBox.tracks);
|
240
|
+
box.target.refer(audioUnitBox);
|
241
|
+
box.type.setValue(inputTrackType);
|
242
|
+
box.index.setValue(index);
|
243
|
+
});
|
244
|
+
return Promise.all(clips.map(clip => readAnyRegion(clip, trackBox)));
|
245
|
+
};
|
246
|
+
const readLane = (lane) => {
|
247
|
+
const track = lane.track; // links to track in structure
|
248
|
+
return Promise.all(lane?.lanes?.map(timeline => {
|
249
|
+
if (isInstanceOf(timeline, ClipsSchema)) {
|
250
|
+
return readTrackRegions(timeline, asDefined(track, "Region(Clips) must have an id."));
|
251
|
+
}
|
252
|
+
else if (isInstanceOf(timeline, PointsSchema)) {
|
253
|
+
// TODO How to get the actual parameter?
|
254
|
+
console.debug(timeline.target?.parameter);
|
255
|
+
}
|
256
|
+
}) ?? []);
|
257
|
+
};
|
258
|
+
const readAnyRegion = (clip, trackBox) => {
|
259
|
+
const createRegion = async (content) => {
|
260
|
+
if (isInstanceOf(content, WarpsSchema)) {
|
261
|
+
await readAnyRegionContent(clip, content, trackBox);
|
262
|
+
}
|
263
|
+
else if (isInstanceOf(content, NotesSchema)) {
|
264
|
+
readNoteRegionContent(clip, content, trackBox);
|
265
|
+
}
|
266
|
+
else if (isInstanceOf(content, ClipsSchema)) {
|
267
|
+
const nested = content.clips.at(0)?.content?.at(0);
|
268
|
+
if (isDefined(nested)) {
|
269
|
+
await createRegion(nested);
|
270
|
+
}
|
271
|
+
}
|
272
|
+
else {
|
273
|
+
console.warn("readAnyRegion > Unknown", content);
|
274
|
+
}
|
275
|
+
};
|
276
|
+
return Promise.all(clip.content?.map(createRegion) ?? []);
|
277
|
+
};
|
278
|
+
const readNoteRegionContent = (clip, notes, trackBox) => {
|
279
|
+
const collectionBox = NoteEventCollectionBox.create(boxGraph, UUID.generate());
|
280
|
+
NoteRegionBox.create(boxGraph, UUID.generate(), box => {
|
281
|
+
const position = asDefined(clip.time, "Time not defined");
|
282
|
+
const duration = asDefined(clip.duration, "Duration not defined");
|
283
|
+
const loopOffset = clip.playStart ?? 0;
|
284
|
+
const loopDuration = clip.loopEnd ?? duration - loopOffset;
|
285
|
+
box.position.setValue(position * PPQN.Quarter);
|
286
|
+
box.duration.setValue(duration * PPQN.Quarter);
|
287
|
+
box.label.setValue(clip.name ?? "");
|
288
|
+
box.loopOffset.setValue(loopOffset * PPQN.Quarter);
|
289
|
+
box.loopDuration.setValue(loopDuration * PPQN.Quarter);
|
290
|
+
box.mute.setValue(clip.enable === false);
|
291
|
+
box.hue.setValue(isUndefined(clip.color)
|
292
|
+
? ColorCodes.forTrackType(trackBox.type.getValue())
|
293
|
+
: Color.hexToHsl(clip.color).h);
|
294
|
+
box.events.refer(collectionBox.owners);
|
295
|
+
box.regions.refer(trackBox.regions);
|
296
|
+
});
|
297
|
+
notes.notes?.forEach(note => {
|
298
|
+
NoteEventBox.create(boxGraph, UUID.generate(), box => {
|
299
|
+
box.position.setValue(note.time * PPQN.Quarter);
|
300
|
+
box.duration.setValue(note.duration * PPQN.Quarter);
|
301
|
+
box.pitch.setValue(note.key);
|
302
|
+
box.velocity.setValue(note.vel ?? 1.0);
|
303
|
+
box.events.refer(collectionBox.events);
|
304
|
+
});
|
305
|
+
});
|
306
|
+
};
|
307
|
+
const readAnyRegionContent = async (clip, warpsSchema, trackBox) => {
|
308
|
+
const audio = warpsSchema.content?.at(0);
|
309
|
+
if (isUndefined(audio)) {
|
310
|
+
return;
|
311
|
+
}
|
312
|
+
const warps = warpsSchema.warps;
|
313
|
+
const warp0 = warps.at(0);
|
314
|
+
const warpN = warps.at(-1);
|
315
|
+
const warpDistance = asDefined(warpN?.time) - asDefined(warp0?.time);
|
316
|
+
const { path, external } = audio.file;
|
317
|
+
assert(external !== true, "File cannot be external");
|
318
|
+
const { uuid, name } = resources.fromPath(path);
|
319
|
+
const audioFileBox = boxGraph.findBox(uuid)
|
320
|
+
.unwrapOrElse(() => AudioFileBox.create(boxGraph, uuid, box => box.fileName.setValue(name)));
|
321
|
+
audioIdSet.add(uuid, true);
|
322
|
+
AudioRegionBox.create(boxGraph, UUID.generate(), box => {
|
323
|
+
const position = asDefined(clip.time, "Time not defined");
|
324
|
+
const duration = asDefined(clip.duration, "Duration not defined");
|
325
|
+
const loopDuration = clip.loopEnd ?? warpDistance;
|
326
|
+
box.position.setValue(position * PPQN.Quarter);
|
327
|
+
box.duration.setValue(duration * PPQN.Quarter);
|
328
|
+
box.label.setValue(clip.name ?? "");
|
329
|
+
box.loopOffset.setValue(0.0);
|
330
|
+
box.loopDuration.setValue(loopDuration * PPQN.Quarter);
|
331
|
+
box.mute.setValue(clip.enable === false);
|
332
|
+
box.regions.refer(trackBox.regions);
|
333
|
+
box.file.refer(audioFileBox);
|
334
|
+
});
|
335
|
+
};
|
336
|
+
return Promise.all(arrangement?.lanes?.lanes?.filter(timeline => isInstanceOf(timeline, LanesSchema))
|
337
|
+
.map(readLane) ?? []);
|
338
|
+
};
|
339
|
+
await ifDefined(schema.arrangement, arrangement => readArrangement(arrangement));
|
340
|
+
outputPointers.forEach(({ target, pointer }) => pointer.refer(asDefined(audioBusses.get(target), `${target} cannot be found.`).input));
|
341
|
+
rootBox.audioUnits.pointerHub.incoming().forEach(({ box }) => {
|
342
|
+
const audioUnitBox = asInstanceOf(box, AudioUnitBox);
|
343
|
+
if (audioUnitBox.type.getValue() !== AudioUnitType.Output
|
344
|
+
&& audioUnitBox.tracks.pointerHub.incoming().length === 0) {
|
345
|
+
const inputTrackType = readInputTrackType(audioUnitBox);
|
346
|
+
TrackBox.create(boxGraph, UUID.generate(), box => {
|
347
|
+
box.tracks.refer(audioUnitBox.tracks);
|
348
|
+
box.target.refer(audioUnitBox);
|
349
|
+
box.type.setValue(inputTrackType);
|
350
|
+
box.index.setValue(0);
|
351
|
+
});
|
352
|
+
}
|
353
|
+
});
|
354
|
+
{
|
355
|
+
let index = 0;
|
356
|
+
sortAudioUnits
|
357
|
+
.sortKeys(NumberComparator)
|
358
|
+
.forEach((_, boxes) => {
|
359
|
+
for (const box of boxes) {
|
360
|
+
if (index === 0) {
|
361
|
+
userInterfaceBox.editingDeviceChain.refer(box.editing);
|
362
|
+
}
|
363
|
+
box.index.setValue(index++);
|
364
|
+
}
|
365
|
+
});
|
366
|
+
}
|
367
|
+
boxGraph.endTransaction();
|
368
|
+
boxGraph.verifyPointers();
|
369
|
+
const { audioBusBox: masterBusBox, audioUnitBox: masterAudioUnit } = primaryAudioBusUnitOption.unwrap("Did not find a primary output");
|
370
|
+
return {
|
371
|
+
audioIds: audioIdSet.values(),
|
372
|
+
skeleton: {
|
373
|
+
boxGraph,
|
374
|
+
mandatoryBoxes: { rootBox, timelineBox, masterBusBox, masterAudioUnit, userInterfaceBox }
|
375
|
+
}
|
376
|
+
};
|
377
|
+
};
|
378
|
+
const readInputTrackType = (audioUnitBox) => {
|
379
|
+
const inputBox = audioUnitBox.input.pointerHub.incoming().at(0)?.box;
|
380
|
+
// TODO Can we find a better way to determine the track type?
|
381
|
+
// Because this would be another location to update when adding new instruments.
|
382
|
+
return inputBox?.accept({
|
383
|
+
visitTapeDeviceBox: () => TrackType.Audio,
|
384
|
+
visitNanoDeviceBox: () => TrackType.Notes,
|
385
|
+
visitPlayfieldDeviceBox: () => TrackType.Notes,
|
386
|
+
visitVaporisateurDeviceBox: () => TrackType.Notes
|
387
|
+
}) ?? TrackType.Undefined;
|
388
|
+
};
|
389
|
+
})(DawProjectImport || (DawProjectImport = {}));
|
@@ -2,15 +2,14 @@ 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 {
|
6
|
-
import {
|
5
|
+
import { DawProject } from "./DawProject";
|
6
|
+
import { DawProjectImport } from "./DawProjectImport";
|
7
7
|
describe("DawProjectImport", () => {
|
8
8
|
it("import", async () => {
|
9
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
10
|
-
const
|
11
|
-
const
|
12
|
-
const
|
13
|
-
|
14
|
-
console.dir(project.structure[1].channel?.devices, { depth: Number.MAX_SAFE_INTEGER });
|
10
|
+
const testFile = "../../../../../test-files/eq.dawproject";
|
11
|
+
const buffer = fs.readFileSync(path.join(__dirname, testFile));
|
12
|
+
const { project, resources } = await DawProject.decode(buffer);
|
13
|
+
const { skeleton } = await DawProjectImport.read(project, resources);
|
15
14
|
});
|
16
15
|
});
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import { Box, BoxGraph } from "@opendaw/lib-box";
|
2
|
+
import { BoxIO } from "@opendaw/studio-boxes";
|
3
|
+
import { DeviceBox } from "@opendaw/studio-adapters";
|
4
|
+
export declare namespace DeviceIO {
|
5
|
+
const exportDevice: (box: Box) => ArrayBufferLike;
|
6
|
+
const importDevice: (boxGraph: BoxGraph<BoxIO.TypeMap>, buffer: ArrayBufferLike) => DeviceBox;
|
7
|
+
}
|
8
|
+
//# sourceMappingURL=DeviceIO.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"DeviceIO.d.ts","sourceRoot":"","sources":["../../src/dawproject/DeviceIO.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,GAAG,EAAE,QAAQ,EAAe,MAAM,kBAAkB,CAAA;AAErE,OAAO,EAAe,KAAK,EAAC,MAAM,uBAAuB,CAAA;AACzD,OAAO,EAAC,SAAS,EAAiB,MAAM,0BAA0B,CAAA;AAElE,yBAAiB,QAAQ,CAAC;IACf,MAAM,YAAY,GAAI,KAAK,GAAG,KAAG,eAiBvC,CAAA;IAEM,MAAM,YAAY,GAAI,UAAU,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,QAAQ,eAAe,KAAG,SAwCzF,CAAA;CACJ"}
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import { PointerField } from "@opendaw/lib-box";
|
2
|
+
import { assert, ByteArrayInput, ByteArrayOutput, panic, UUID } from "@opendaw/lib-std";
|
3
|
+
import { DeviceBoxUtils } from "@opendaw/studio-adapters";
|
4
|
+
export var DeviceIO;
|
5
|
+
(function (DeviceIO) {
|
6
|
+
DeviceIO.exportDevice = (box) => {
|
7
|
+
const dependencies = Array.from(box.graph.dependenciesOf(box).boxes);
|
8
|
+
const output = ByteArrayOutput.create();
|
9
|
+
output.writeString("openDAW:device");
|
10
|
+
output.writeInt(1); // format version
|
11
|
+
const writeBox = (box) => {
|
12
|
+
UUID.toDataOutput(output, box.address.uuid);
|
13
|
+
output.writeString(box.name);
|
14
|
+
const arrayBuffer = box.toArrayBuffer();
|
15
|
+
output.writeInt(arrayBuffer.byteLength);
|
16
|
+
output.writeBytes(new Int8Array(arrayBuffer));
|
17
|
+
};
|
18
|
+
writeBox(box);
|
19
|
+
output.writeInt(dependencies.length);
|
20
|
+
dependencies.forEach(dep => writeBox(dep));
|
21
|
+
return output.toArrayBuffer();
|
22
|
+
};
|
23
|
+
DeviceIO.importDevice = (boxGraph, buffer) => {
|
24
|
+
const input = new ByteArrayInput(buffer);
|
25
|
+
const header = input.readString();
|
26
|
+
const version = input.readInt();
|
27
|
+
assert(header === "openDAW:device", `wrong header: ${header}`);
|
28
|
+
assert(version === 1, `wrong version: ${version}`);
|
29
|
+
const mapping = UUID.newSet(({ source }) => source);
|
30
|
+
const rawBoxes = [];
|
31
|
+
const readRawBox = () => {
|
32
|
+
const uuid = UUID.fromDataInput(input);
|
33
|
+
const key = input.readString();
|
34
|
+
const length = input.readInt();
|
35
|
+
const array = new Int8Array(length);
|
36
|
+
input.readBytes(array);
|
37
|
+
mapping.add({ source: uuid, target: key === "AudioFileBox" ? uuid : UUID.generate() });
|
38
|
+
return { uuid, key, input: new ByteArrayInput(array.buffer) };
|
39
|
+
};
|
40
|
+
rawBoxes.push(readRawBox());
|
41
|
+
const numDeps = input.readInt();
|
42
|
+
for (let i = 0; i < numDeps; i++) {
|
43
|
+
rawBoxes.push(readRawBox());
|
44
|
+
}
|
45
|
+
// We are going to award all boxes with new UUIDs.
|
46
|
+
// Therefore, we need to map all internal pointer targets.
|
47
|
+
return PointerField.decodeWith({
|
48
|
+
map: (_pointer, newAddress) => newAddress.map(address => mapping.opt(address.uuid).match({
|
49
|
+
none: () => address,
|
50
|
+
some: ({ target }) => address.moveTo(target)
|
51
|
+
}))
|
52
|
+
}, () => {
|
53
|
+
const [main, ...deps] = rawBoxes;
|
54
|
+
const { key, uuid, input } = main;
|
55
|
+
const box = boxGraph.createBox(key, mapping.get(uuid).target, box => box.read(input));
|
56
|
+
if (!DeviceBoxUtils.isDeviceBox(box)) {
|
57
|
+
return panic(`${box.name} is not a DeviceBox`);
|
58
|
+
}
|
59
|
+
deps.forEach(({ key, uuid, input }) => boxGraph.createBox(key, mapping.get(uuid).target, box => box.read(input)));
|
60
|
+
return box;
|
61
|
+
});
|
62
|
+
};
|
63
|
+
})(DeviceIO || (DeviceIO = {}));
|
package/dist/index.d.ts
CHANGED
@@ -1,14 +1,22 @@
|
|
1
1
|
export * from "./sync-log/Commit";
|
2
2
|
export * from "./sync-log/SyncLogReader";
|
3
3
|
export * from "./sync-log/SyncLogWriter";
|
4
|
-
export * from "./samples/SamplePeaks";
|
5
4
|
export * from "./samples/SampleStorage";
|
6
5
|
export * from "./samples/MainThreadSampleLoader";
|
7
6
|
export * from "./samples/MainThreadSampleManager";
|
8
7
|
export * from "./samples/SampleProvider";
|
9
|
-
export * from "./
|
8
|
+
export * from "./capture/Capture";
|
9
|
+
export * from "./capture/CaptureAudio";
|
10
|
+
export * from "./capture/CaptureMidi";
|
11
|
+
export * from "./capture/CaptureManager";
|
12
|
+
export * from "./capture/Recording";
|
13
|
+
export * from "./capture/RecordingContext";
|
14
|
+
export * from "./dawproject/DawProject";
|
10
15
|
export * from "./dawproject/DawProjectExporter";
|
11
|
-
export * from "./dawproject/
|
16
|
+
export * from "./dawproject/DawProjectImport";
|
17
|
+
export * from "./dawproject/DawProjectImport";
|
18
|
+
export * from "./AudioInputDevices";
|
19
|
+
export * from "./AudioUnitOrdering";
|
12
20
|
export * from "./ColorCodes";
|
13
21
|
export * from "./Colors";
|
14
22
|
export * from "./EffectBox";
|
package/dist/index.d.ts.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA;AAExC,cAAc,
|
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,yBAAyB,CAAA;AACvC,cAAc,kCAAkC,CAAA;AAChD,cAAc,mCAAmC,CAAA;AACjD,cAAc,0BAA0B,CAAA;AAExC,cAAc,mBAAmB,CAAA;AACjC,cAAc,wBAAwB,CAAA;AACtC,cAAc,uBAAuB,CAAA;AACrC,cAAc,0BAA0B,CAAA;AACxC,cAAc,qBAAqB,CAAA;AACnC,cAAc,4BAA4B,CAAA;AAE1C,cAAc,yBAAyB,CAAA;AACvC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,+BAA+B,CAAA;AAE7C,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,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
@@ -1,14 +1,22 @@
|
|
1
1
|
export * from "./sync-log/Commit";
|
2
2
|
export * from "./sync-log/SyncLogReader";
|
3
3
|
export * from "./sync-log/SyncLogWriter";
|
4
|
-
export * from "./samples/SamplePeaks";
|
5
4
|
export * from "./samples/SampleStorage";
|
6
5
|
export * from "./samples/MainThreadSampleLoader";
|
7
6
|
export * from "./samples/MainThreadSampleManager";
|
8
7
|
export * from "./samples/SampleProvider";
|
9
|
-
export * from "./
|
8
|
+
export * from "./capture/Capture";
|
9
|
+
export * from "./capture/CaptureAudio";
|
10
|
+
export * from "./capture/CaptureMidi";
|
11
|
+
export * from "./capture/CaptureManager";
|
12
|
+
export * from "./capture/Recording";
|
13
|
+
export * from "./capture/RecordingContext";
|
14
|
+
export * from "./dawproject/DawProject";
|
10
15
|
export * from "./dawproject/DawProjectExporter";
|
11
|
-
export * from "./dawproject/
|
16
|
+
export * from "./dawproject/DawProjectImport";
|
17
|
+
export * from "./dawproject/DawProjectImport";
|
18
|
+
export * from "./AudioInputDevices";
|
19
|
+
export * from "./AudioUnitOrdering";
|
12
20
|
export * from "./ColorCodes";
|
13
21
|
export * from "./Colors";
|
14
22
|
export * from "./EffectBox";
|