@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.
Files changed (108) hide show
  1. package/dist/AudioInputDevices.d.ts +8 -0
  2. package/dist/AudioInputDevices.d.ts.map +1 -0
  3. package/dist/AudioInputDevices.js +30 -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/Project.d.ts +4 -2
  20. package/dist/Project.d.ts.map +1 -1
  21. package/dist/Project.js +6 -3
  22. package/dist/ProjectApi.d.ts +0 -1
  23. package/dist/ProjectApi.d.ts.map +1 -1
  24. package/dist/ProjectApi.js +16 -11
  25. package/dist/ProjectEnv.d.ts +1 -0
  26. package/dist/ProjectEnv.d.ts.map +1 -1
  27. package/dist/ProjectMigration.d.ts.map +1 -1
  28. package/dist/ProjectMigration.js +20 -3
  29. package/dist/RecordingWorklet.d.ts +15 -3
  30. package/dist/RecordingWorklet.d.ts.map +1 -1
  31. package/dist/RecordingWorklet.js +111 -16
  32. package/dist/WorkerAgents.d.ts +2 -2
  33. package/dist/WorkerAgents.d.ts.map +1 -1
  34. package/dist/Worklets.d.ts +1 -1
  35. package/dist/Worklets.d.ts.map +1 -1
  36. package/dist/Worklets.js +2 -2
  37. package/dist/capture/Capture.d.ts +22 -0
  38. package/dist/capture/Capture.d.ts.map +1 -0
  39. package/dist/capture/Capture.js +32 -0
  40. package/dist/capture/CaptureAudio.d.ts +17 -0
  41. package/dist/capture/CaptureAudio.d.ts.map +1 -0
  42. package/dist/capture/CaptureAudio.js +106 -0
  43. package/dist/capture/CaptureManager.d.ts +12 -0
  44. package/dist/capture/CaptureManager.d.ts.map +1 -0
  45. package/dist/capture/CaptureManager.js +38 -0
  46. package/dist/capture/CaptureMidi.d.ts +13 -0
  47. package/dist/capture/CaptureMidi.d.ts.map +1 -0
  48. package/dist/capture/CaptureMidi.js +50 -0
  49. package/dist/capture/RecordAudio.d.ts +21 -0
  50. package/dist/capture/RecordAudio.d.ts.map +1 -0
  51. package/dist/capture/RecordAudio.js +66 -0
  52. package/dist/capture/RecordMidi.d.ts +15 -0
  53. package/dist/capture/RecordMidi.d.ts.map +1 -0
  54. package/dist/capture/RecordMidi.js +85 -0
  55. package/dist/capture/RecordTrack.d.ts +7 -0
  56. package/dist/capture/RecordTrack.d.ts.map +1 -0
  57. package/dist/capture/RecordTrack.js +23 -0
  58. package/dist/capture/Recording.d.ts +9 -0
  59. package/dist/capture/Recording.d.ts.map +1 -0
  60. package/dist/capture/Recording.js +65 -0
  61. package/dist/capture/RecordingContext.d.ts +14 -0
  62. package/dist/capture/RecordingContext.d.ts.map +1 -0
  63. package/dist/capture/RecordingContext.js +1 -0
  64. package/dist/dawproject/AudioUnitExportLayout.d.ts +10 -0
  65. package/dist/dawproject/AudioUnitExportLayout.d.ts.map +1 -0
  66. package/dist/dawproject/AudioUnitExportLayout.js +64 -0
  67. package/dist/dawproject/BuiltinDevices.d.ts +9 -0
  68. package/dist/dawproject/BuiltinDevices.d.ts.map +1 -0
  69. package/dist/dawproject/BuiltinDevices.js +70 -0
  70. package/dist/dawproject/{DawProjectIO.d.ts → DawProject.d.ts} +6 -4
  71. package/dist/dawproject/DawProject.d.ts.map +1 -0
  72. package/dist/dawproject/{DawProjectIO.js → DawProject.js} +23 -5
  73. package/dist/dawproject/DawProjectExporter.d.ts +7 -6
  74. package/dist/dawproject/DawProjectExporter.d.ts.map +1 -1
  75. package/dist/dawproject/DawProjectExporter.js +245 -22
  76. package/dist/dawproject/DawProjectExporter.test.js +39 -6
  77. package/dist/dawproject/DawProjectImport.d.ts +12 -0
  78. package/dist/dawproject/DawProjectImport.d.ts.map +1 -0
  79. package/dist/dawproject/DawProjectImport.js +389 -0
  80. package/dist/dawproject/DawProjectImport.test.js +6 -7
  81. package/dist/dawproject/DeviceIO.d.ts +8 -0
  82. package/dist/dawproject/DeviceIO.d.ts.map +1 -0
  83. package/dist/dawproject/DeviceIO.js +63 -0
  84. package/dist/index.d.ts +11 -3
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +11 -3
  87. package/dist/processors.js +3 -3
  88. package/dist/processors.js.map +4 -4
  89. package/dist/samples/MainThreadSampleLoader.d.ts +1 -0
  90. package/dist/samples/MainThreadSampleLoader.d.ts.map +1 -1
  91. package/dist/samples/MainThreadSampleLoader.js +10 -6
  92. package/dist/samples/MainThreadSampleManager.d.ts +5 -5
  93. package/dist/samples/MainThreadSampleManager.d.ts.map +1 -1
  94. package/dist/samples/MainThreadSampleManager.js +1 -0
  95. package/dist/samples/SampleProvider.d.ts +2 -2
  96. package/dist/samples/SampleProvider.d.ts.map +1 -1
  97. package/dist/samples/SampleStorage.d.ts.map +1 -1
  98. package/dist/samples/SampleStorage.js +2 -3
  99. package/dist/workers.js +2 -2
  100. package/dist/workers.js.map +4 -4
  101. package/package.json +15 -15
  102. package/dist/dawproject/DawProjectIO.d.ts.map +0 -1
  103. package/dist/dawproject/DawProjectImporter.d.ts +0 -12
  104. package/dist/dawproject/DawProjectImporter.d.ts.map +0 -1
  105. package/dist/dawproject/DawProjectImporter.js +0 -273
  106. package/dist/samples/SamplePeaks.d.ts +0 -6
  107. package/dist/samples/SamplePeaks.d.ts.map +0 -1
  108. 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 { DawProjectIO } from "./DawProjectIO";
6
- import { DawProjectImporter } from "./DawProjectImporter";
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 buffer = fs.readFileSync(path.join(__dirname, "../../../../../test-files/test.dawproject"));
11
- const { project, resources } = await DawProjectIO.decode(buffer);
12
- const importer = await DawProjectImporter.importProject(project, resources);
13
- console.debug(importer.skeleton);
14
- console.dir(project.structure[1].channel?.devices, { depth: Number.MAX_SAFE_INTEGER });
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 "./dawproject/DawProjectIO";
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/DawProjectImporter";
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";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA;AAExC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA;AACvC,cAAc,kCAAkC,CAAA;AAChD,cAAc,mCAAmC,CAAA;AACjD,cAAc,0BAA0B,CAAA;AAExC,cAAc,2BAA2B,CAAA;AACzC,cAAc,iCAAiC,CAAA;AAC/C,cAAc,iCAAiC,CAAA;AAE/C,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,mBAAmB,CAAA;AACjC,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,OAAO,CAAA;AACrB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,YAAY,CAAA"}
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 "./dawproject/DawProjectIO";
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/DawProjectImporter";
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";