@opendaw/studio-core 0.0.83 → 0.0.85
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/EffectFactories.d.ts +0 -2
- package/dist/EffectFactories.d.ts.map +1 -1
- package/dist/EffectFactories.js +1 -1
- package/dist/Engine.d.ts +2 -1
- package/dist/Engine.d.ts.map +1 -1
- package/dist/EngineFacade.d.ts +2 -1
- package/dist/EngineFacade.d.ts.map +1 -1
- package/dist/EngineFacade.js +3 -1
- package/dist/EngineWorklet.d.ts +2 -1
- package/dist/EngineWorklet.d.ts.map +1 -1
- package/dist/EngineWorklet.js +5 -2
- package/dist/ExternalLib.d.ts +3 -2
- package/dist/ExternalLib.d.ts.map +1 -1
- package/dist/ExternalLib.js +2 -2
- package/dist/Preferences.d.ts +6 -0
- package/dist/Preferences.d.ts.map +1 -1
- package/dist/Preferences.js +5 -2
- package/dist/Storage.d.ts.map +1 -1
- package/dist/Storage.js +14 -5
- package/dist/capture/Capture.d.ts +4 -1
- package/dist/capture/Capture.d.ts.map +1 -1
- package/dist/capture/Capture.js +4 -0
- package/dist/capture/CaptureDevices.js +1 -1
- package/dist/capture/RecordAudio.d.ts.map +1 -1
- package/dist/capture/RecordAudio.js +20 -10
- package/dist/capture/RecordMidi.d.ts.map +1 -1
- package/dist/capture/RecordMidi.js +1 -0
- package/dist/capture/Recording.d.ts.map +1 -1
- package/dist/capture/Recording.js +1 -0
- package/dist/dawproject/BuiltinDevices.d.ts +1 -1
- package/dist/dawproject/DawProject.d.ts.map +1 -1
- package/dist/dawproject/DawProject.js +17 -3
- package/dist/midi/MIDILearning.d.ts +4 -16
- package/dist/midi/MIDILearning.d.ts.map +1 -1
- package/dist/midi/MIDILearning.js +94 -62
- package/dist/processors.js +17 -17
- package/dist/processors.js.map +4 -4
- package/dist/project/Project.d.ts +2 -2
- package/dist/project/Project.d.ts.map +1 -1
- package/dist/project/Project.js +16 -9
- package/dist/project/ProjectApi.js +2 -2
- package/dist/project/ProjectBundle.d.ts.map +1 -1
- package/dist/project/ProjectBundle.js +21 -6
- package/dist/project/ProjectMigration.d.ts.map +1 -1
- package/dist/project/ProjectMigration.js +20 -4
- package/dist/project/ProjectValidation.d.ts.map +1 -1
- package/dist/project/ProjectValidation.js +15 -13
- package/dist/project/audio/AudioContentFactory.d.ts.map +1 -1
- package/dist/project/audio/AudioContentFactory.js +3 -3
- package/dist/project/audio/AudioContentModifier.d.ts.map +1 -1
- package/dist/project/audio/AudioContentModifier.js +15 -3
- package/dist/project/audio/AudioFileBoxFactory.d.ts.map +1 -1
- package/dist/project/audio/AudioFileBoxFactory.js +5 -0
- package/dist/samples/DefaultSampleLoader.d.ts.map +1 -1
- package/dist/samples/DefaultSampleLoader.js +8 -4
- package/dist/samples/DefaultSampleLoaderManager.d.ts.map +1 -1
- package/dist/samples/DefaultSampleLoaderManager.js +5 -2
- package/dist/soundfont/DefaultSoundfontLoader.d.ts.map +1 -1
- package/dist/soundfont/DefaultSoundfontLoader.js +16 -11
- package/dist/soundfont/SoundfontService.d.ts.map +1 -1
- package/dist/soundfont/SoundfontService.js +3 -6
- package/dist/ui/RegionClipResolver.d.ts.map +1 -1
- package/dist/ui/RegionClipResolver.js +15 -6
- package/dist/ui/RegionModifyStrategies.js +3 -3
- package/dist/ui/TimeGrid.d.ts +2 -1
- package/dist/ui/TimeGrid.d.ts.map +1 -1
- package/dist/ui/TimeGrid.js +28 -14
- package/dist/workers-main.js +1 -1
- package/dist/workers-main.js.map +3 -3
- package/package.json +16 -16
|
@@ -10,7 +10,7 @@ import { CaptureDevices } from "../capture";
|
|
|
10
10
|
import { EngineFacade } from "../EngineFacade";
|
|
11
11
|
import { EngineWorklet } from "../EngineWorklet";
|
|
12
12
|
import { MIDILearning } from "../midi";
|
|
13
|
-
import {
|
|
13
|
+
import { TempoMap } from "@opendaw/lib-dsp";
|
|
14
14
|
export type RestartWorklet = {
|
|
15
15
|
unload: Func<unknown, Promise<unknown>>;
|
|
16
16
|
load: Procedure<EngineWorklet>;
|
|
@@ -58,10 +58,10 @@ export declare class Project implements BoxAdaptersContext, Terminable, Terminab
|
|
|
58
58
|
get isAudioContext(): boolean;
|
|
59
59
|
get isMainThread(): boolean;
|
|
60
60
|
get liveStreamBroadcaster(): LiveStreamBroadcaster;
|
|
61
|
-
get signatureDuration(): ppqn;
|
|
62
61
|
get skeleton(): ProjectSkeleton;
|
|
63
62
|
receivedMIDIFromEngine(midiDeviceId: string, data: Uint8Array, relativeTimeInMs: number): void;
|
|
64
63
|
collectSampleUUIDs(): ReadonlyArray<UUID.Bytes>;
|
|
64
|
+
restartRecording(): void;
|
|
65
65
|
toArrayBuffer(): ArrayBufferLike;
|
|
66
66
|
copy(env?: Partial<ProjectEnv>): Project;
|
|
67
67
|
invalid(): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Project.d.ts","sourceRoot":"","sources":["../../src/project/Project.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,IAAI,EAEJ,SAAS,EAET,UAAU,EACV,eAAe,EACf,UAAU,EACV,IAAI,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAC,UAAU,EAAE,QAAQ,EAAC,MAAM,kBAAkB,CAAA;AACrD,OAAO,EACH,WAAW,EAGX,YAAY,EACZ,KAAK,EAEL,OAAO,EACP,WAAW,EAEX,gBAAgB,EACnB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACH,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,sBAAsB,EACtB,gBAAgB,EAEhB,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAElB,kBAAkB,
|
|
1
|
+
{"version":3,"file":"Project.d.ts","sourceRoot":"","sources":["../../src/project/Project.ts"],"names":[],"mappings":"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"}
|
package/dist/project/Project.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Arrays, panic, safeExecute, Terminator } from "@opendaw/lib-std";
|
|
2
2
|
import { BoxEditing } from "@opendaw/lib-box";
|
|
3
3
|
import { AudioRegionBox } from "@opendaw/studio-boxes";
|
|
4
|
-
import { BoxAdapters, ParameterFieldAdapters, ProjectSkeleton, RootBoxAdapter, TimelineBoxAdapter, UnionBoxTypes, UserEditingManager, VertexSelection } from "@opendaw/studio-adapters";
|
|
4
|
+
import { BoxAdapters, ParameterFieldAdapters, ProjectSkeleton, RootBoxAdapter, TimelineBoxAdapter, UnionBoxTypes, UserEditingManager, VaryingTempoMap, VertexSelection } from "@opendaw/studio-adapters";
|
|
5
5
|
import { LiveStreamReceiver } from "@opendaw/lib-fusion";
|
|
6
6
|
import { Mixer } from "../Mixer";
|
|
7
7
|
import { ProjectApi } from "./ProjectApi";
|
|
@@ -11,7 +11,7 @@ import { EngineFacade } from "../EngineFacade";
|
|
|
11
11
|
import { MidiDevices, MIDILearning } from "../midi";
|
|
12
12
|
import { ProjectValidation } from "./ProjectValidation";
|
|
13
13
|
import { Preferences } from "../Preferences";
|
|
14
|
-
import {
|
|
14
|
+
import { TimeBase } from "@opendaw/lib-dsp";
|
|
15
15
|
import { MidiData } from "@opendaw/lib-midi";
|
|
16
16
|
// Main Entry Point for a Project
|
|
17
17
|
export class Project {
|
|
@@ -77,8 +77,8 @@ export class Project {
|
|
|
77
77
|
this.editing = new BoxEditing(this.boxGraph);
|
|
78
78
|
this.selection = new VertexSelection(this.editing, this.boxGraph);
|
|
79
79
|
this.parameterFieldAdapters = new ParameterFieldAdapters();
|
|
80
|
-
this.tempoMap = new ConstantTempoMap(this.timelineBox.bpm);
|
|
81
80
|
this.boxAdapters = this.#terminator.own(new BoxAdapters(this));
|
|
81
|
+
this.tempoMap = new VaryingTempoMap(this.timelineBoxAdapter);
|
|
82
82
|
this.userEditingManager = new UserEditingManager(this.editing);
|
|
83
83
|
this.liveStreamReceiver = this.#terminator.own(new LiveStreamReceiver());
|
|
84
84
|
this.midiLearning = this.#terminator.own(new MIDILearning(this));
|
|
@@ -118,6 +118,7 @@ export class Project {
|
|
|
118
118
|
}
|
|
119
119
|
follow(box) {
|
|
120
120
|
this.userEditingManager.follow(box);
|
|
121
|
+
this.midiLearning.followUser(box.midiControllers);
|
|
121
122
|
this.selection.switch(box.selection);
|
|
122
123
|
}
|
|
123
124
|
own(terminable) { return this.#terminator.own(terminable); }
|
|
@@ -132,10 +133,6 @@ export class Project {
|
|
|
132
133
|
get isAudioContext() { return false; }
|
|
133
134
|
get isMainThread() { return true; }
|
|
134
135
|
get liveStreamBroadcaster() { return panic("Only available in audio context"); }
|
|
135
|
-
get signatureDuration() {
|
|
136
|
-
const { nominator, denominator } = this.timelineBox.signature;
|
|
137
|
-
return PPQN.fromSignature(nominator.getValue(), denominator.getValue());
|
|
138
|
-
}
|
|
139
136
|
get skeleton() {
|
|
140
137
|
return {
|
|
141
138
|
boxGraph: this.boxGraph,
|
|
@@ -168,9 +165,19 @@ export class Project {
|
|
|
168
165
|
.filter(box => box.accept({ visitAudioFileBox: (_box) => true }))
|
|
169
166
|
.map(box => box.address.uuid);
|
|
170
167
|
}
|
|
171
|
-
|
|
172
|
-
|
|
168
|
+
restartRecording() {
|
|
169
|
+
if (this.engine.isRecording.getValue()) {
|
|
170
|
+
this.engine.stopRecording();
|
|
171
|
+
this.editing.modify(() => this.captureDevices.filterArmed()
|
|
172
|
+
.forEach(capture => {
|
|
173
|
+
capture.recordedRegions().forEach(region => region.box.delete());
|
|
174
|
+
capture.clearRecordedRegions();
|
|
175
|
+
}), false);
|
|
176
|
+
this.engine.stop(true);
|
|
177
|
+
setTimeout(() => this.startRecording(true), 100);
|
|
178
|
+
}
|
|
173
179
|
}
|
|
180
|
+
toArrayBuffer() { return ProjectSkeleton.encode(this.boxGraph); }
|
|
174
181
|
copy(env) {
|
|
175
182
|
return Project.load({ ...this.#env, ...env }, this.toArrayBuffer());
|
|
176
183
|
}
|
|
@@ -176,7 +176,7 @@ export class ProjectApi {
|
|
|
176
176
|
box.hue.setValue(hue ?? ColorCodes.forTrackType(type));
|
|
177
177
|
box.mute.setValue(false);
|
|
178
178
|
box.duration.setValue(duration);
|
|
179
|
-
box.loopDuration.setValue(
|
|
179
|
+
box.loopDuration.setValue(duration);
|
|
180
180
|
box.events.refer(events.owners);
|
|
181
181
|
box.regions.refer(trackBox.regions);
|
|
182
182
|
}));
|
|
@@ -189,7 +189,7 @@ export class ProjectApi {
|
|
|
189
189
|
box.hue.setValue(hue ?? ColorCodes.forTrackType(type));
|
|
190
190
|
box.mute.setValue(false);
|
|
191
191
|
box.duration.setValue(duration);
|
|
192
|
-
box.loopDuration.setValue(
|
|
192
|
+
box.loopDuration.setValue(duration);
|
|
193
193
|
box.events.refer(events.owners);
|
|
194
194
|
box.regions.refer(trackBox.regions);
|
|
195
195
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectBundle.d.ts","sourceRoot":"","sources":["../../src/project/ProjectBundle.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4C,QAAQ,
|
|
1
|
+
{"version":3,"file":"ProjectBundle.d.ts","sourceRoot":"","sources":["../../src/project/ProjectBundle.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4C,QAAQ,EAAmB,IAAI,EAAC,MAAM,kBAAkB,CAAA;AAI3G,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAA;AAEvC,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAO/C,yBAAiB,aAAa,CAAC;IACpB,MAAM,MAAM,GAAU,gCAA8B,cAAc,EAC5C,UAAU,QAAQ,CAAC,OAAO,KAAG,OAAO,CAAC,WAAW,CA2C5E,CAAA;IAEM,MAAM,MAAM,GAAU,KAAK,UAAU,EACf,aAAa,WAAW,EACxB,kBAAkB,IAAI,CAAC,KAAK,KAAG,OAAO,CAAC,cAAc,CA6CjF,CAAA;CAgDJ"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { asDefined, isDefined, Option, panic, UUID } from "@opendaw/lib-std";
|
|
1
|
+
import { asDefined, isDefined, Option, panic, RuntimeNotifier, UUID } from "@opendaw/lib-std";
|
|
2
2
|
import { AudioFileBox, SoundfontFileBox } from "@opendaw/studio-boxes";
|
|
3
3
|
import { Project } from "./Project";
|
|
4
4
|
import { ProjectPaths } from "./ProjectPaths";
|
|
@@ -10,7 +10,14 @@ import { ExternalLib } from "../ExternalLib";
|
|
|
10
10
|
export var ProjectBundle;
|
|
11
11
|
(function (ProjectBundle) {
|
|
12
12
|
ProjectBundle.encode = async ({ uuid, project, meta, cover }, progress) => {
|
|
13
|
-
const JSZip = await ExternalLib.JSZip();
|
|
13
|
+
const { status, value: JSZip, error } = await ExternalLib.JSZip();
|
|
14
|
+
if (status === "rejected") {
|
|
15
|
+
await RuntimeNotifier.info({
|
|
16
|
+
headline: "Error",
|
|
17
|
+
message: `Could not load JSZip: ${String(error)}`
|
|
18
|
+
});
|
|
19
|
+
return Promise.reject(error);
|
|
20
|
+
}
|
|
14
21
|
const zip = new JSZip();
|
|
15
22
|
zip.file("version", "1");
|
|
16
23
|
zip.file("uuid", uuid, { binary: true });
|
|
@@ -45,7 +52,14 @@ export var ProjectBundle;
|
|
|
45
52
|
return blob.arrayBuffer();
|
|
46
53
|
};
|
|
47
54
|
ProjectBundle.decode = async (env, arrayBuffer, openProfileUUID) => {
|
|
48
|
-
const JSZip = await ExternalLib.JSZip();
|
|
55
|
+
const { status, value: JSZip, error } = await ExternalLib.JSZip();
|
|
56
|
+
if (status === "rejected") {
|
|
57
|
+
await RuntimeNotifier.info({
|
|
58
|
+
headline: "Error",
|
|
59
|
+
message: `Could not load JSZip: ${String(error)}`
|
|
60
|
+
});
|
|
61
|
+
return Promise.reject(error);
|
|
62
|
+
}
|
|
49
63
|
const zip = await JSZip.loadAsync(arrayBuffer);
|
|
50
64
|
if (await asDefined(zip.file("version")).async("text") !== "1") {
|
|
51
65
|
return panic("Unknown bundle version");
|
|
@@ -80,7 +94,8 @@ export var ProjectBundle;
|
|
|
80
94
|
});
|
|
81
95
|
}
|
|
82
96
|
await Promise.all(promises);
|
|
83
|
-
const
|
|
97
|
+
const projectData = await asDefined(zip.file(ProjectPaths.ProjectFile)).async("arraybuffer");
|
|
98
|
+
const project = await Project.loadAnyVersion(env, projectData);
|
|
84
99
|
const meta = JSON.parse(await asDefined(zip.file(ProjectPaths.ProjectMetaFile)).async("text"));
|
|
85
100
|
const coverFile = zip.file(ProjectPaths.ProjectCoverFile);
|
|
86
101
|
const cover = Option.wrap(await coverFile?.async("arraybuffer"));
|
|
@@ -104,7 +119,7 @@ export var ProjectBundle;
|
|
|
104
119
|
subscription.terminate();
|
|
105
120
|
}
|
|
106
121
|
else if (state.type === "error") {
|
|
107
|
-
reject(state.reason);
|
|
122
|
+
reject(new Error(state.reason));
|
|
108
123
|
subscription.terminate();
|
|
109
124
|
}
|
|
110
125
|
});
|
|
@@ -128,7 +143,7 @@ export var ProjectBundle;
|
|
|
128
143
|
subscription.terminate();
|
|
129
144
|
}
|
|
130
145
|
else if (state.type === "error") {
|
|
131
|
-
reject(state.reason);
|
|
146
|
+
reject(new Error(state.reason));
|
|
132
147
|
subscription.terminate();
|
|
133
148
|
}
|
|
134
149
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectMigration.d.ts","sourceRoot":"","sources":["../../src/project/ProjectMigration.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProjectMigration.d.ts","sourceRoot":"","sources":["../../src/project/ProjectMigration.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAC,eAAe,EAAC,MAAM,0BAA0B,CAAA;AAIxD,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAA;AASvC,qBAAa,gBAAgB;WACZ,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,EAAC,QAAQ,EAAE,cAAc,EAAC,EAAE,eAAe;CAuPpF"}
|
|
@@ -26,18 +26,26 @@ export class ProjectMigration {
|
|
|
26
26
|
const loadAudioData = (uuid) => {
|
|
27
27
|
const { promise, resolve, reject } = Promise.withResolvers();
|
|
28
28
|
const loader = env.sampleManager.getOrCreate(uuid);
|
|
29
|
-
|
|
29
|
+
let subscription;
|
|
30
|
+
subscription = loader.subscribe(state => {
|
|
30
31
|
if (state.type === "loaded") {
|
|
31
|
-
subscription.terminate();
|
|
32
|
+
queueMicrotask(() => subscription.terminate());
|
|
32
33
|
resolve(loader.data.unwrap("State mismatch"));
|
|
33
34
|
}
|
|
34
35
|
else if (state.type === "error") {
|
|
35
|
-
subscription.terminate();
|
|
36
|
-
reject(state.reason);
|
|
36
|
+
queueMicrotask(() => subscription.terminate());
|
|
37
|
+
reject(new Error(state.reason));
|
|
37
38
|
}
|
|
38
39
|
});
|
|
39
40
|
return promise;
|
|
40
41
|
};
|
|
42
|
+
const orphans = boxGraph.findOrphans(rootBox);
|
|
43
|
+
if (orphans.length > 0) {
|
|
44
|
+
console.debug("Migrate remove orphaned boxes: ", orphans.length);
|
|
45
|
+
boxGraph.beginTransaction();
|
|
46
|
+
orphans.forEach(orphan => orphan.delete());
|
|
47
|
+
boxGraph.endTransaction();
|
|
48
|
+
}
|
|
41
49
|
// 1st pass (2nd pass might rely on those changes)
|
|
42
50
|
for (const box of boxGraph.boxes()) {
|
|
43
51
|
await box.accept({
|
|
@@ -130,6 +138,14 @@ export class ProjectMigration {
|
|
|
130
138
|
boxGraph.endTransaction();
|
|
131
139
|
}
|
|
132
140
|
},
|
|
141
|
+
visitTimelineBox: (timelineBox) => {
|
|
142
|
+
if (timelineBox.tempoTrack.events.isEmpty()) {
|
|
143
|
+
console.debug("Migrate 'TimelineBox' to have a ValueEventCollectionBox for tempo events");
|
|
144
|
+
boxGraph.beginTransaction();
|
|
145
|
+
ValueEventCollectionBox.create(boxGraph, UUID.generate(), box => timelineBox.tempoTrack.events.refer(box.owners));
|
|
146
|
+
boxGraph.endTransaction();
|
|
147
|
+
}
|
|
148
|
+
},
|
|
133
149
|
visitMIDIOutputDeviceBox: (deviceBox) => {
|
|
134
150
|
const id = deviceBox.deprecatedDevice.id.getValue();
|
|
135
151
|
const label = deviceBox.deprecatedDevice.label.getValue();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectValidation.d.ts","sourceRoot":"","sources":["../../src/project/ProjectValidation.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,eAAe,EAAC,MAAM,0BAA0B,CAAA;AAGxD,yBAAiB,iBAAiB,CAAC;IACxB,MAAM,QAAQ,GAAI,UAAU,eAAe,KAAG,
|
|
1
|
+
{"version":3,"file":"ProjectValidation.d.ts","sourceRoot":"","sources":["../../src/project/ProjectValidation.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,eAAe,EAAC,MAAM,0BAA0B,CAAA;AAGxD,yBAAiB,iBAAiB,CAAC;IACxB,MAAM,QAAQ,GAAI,UAAU,eAAe,KAAG,IA6CpD,CAAA;CACJ"}
|
|
@@ -18,20 +18,22 @@ export var ProjectValidation;
|
|
|
18
18
|
visitNoteRegionBox: (box) => validateRegion(box),
|
|
19
19
|
visitValueRegionBox: (box) => validateRegion(box),
|
|
20
20
|
visitAudioRegionBox: (box) => validateRegion(box),
|
|
21
|
-
visitTrackBox: (box) =>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
visitTrackBox: (box) => {
|
|
22
|
+
const regions = box.regions.pointerHub.incoming()
|
|
23
|
+
.map(({ box }) => asDefined(box.accept({
|
|
24
|
+
visitNoteRegionBox: (box) => box,
|
|
25
|
+
visitValueRegionBox: (box) => box,
|
|
26
|
+
visitAudioRegionBox: (box) => box
|
|
27
|
+
}), "Box must be a NoteRegionBox, ValueRegionBox or AudioRegionBox"))
|
|
28
|
+
.sort((a, b) => a.position.getValue() - b.position.getValue());
|
|
29
|
+
for (const [left, right] of Arrays.iterateAdjacent(regions)) {
|
|
30
|
+
if (right.position.getValue() < left.position.getValue() + left.duration.getValue()) {
|
|
31
|
+
console.warn(left, right, "Overlapping regions");
|
|
32
|
+
invalidBoxes.add(left);
|
|
33
|
+
invalidBoxes.add(right);
|
|
34
|
+
}
|
|
33
35
|
}
|
|
34
|
-
}
|
|
36
|
+
}
|
|
35
37
|
}));
|
|
36
38
|
if (invalidBoxes.size === 0) {
|
|
37
39
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioContentFactory.d.ts","sourceRoot":"","sources":["../../../src/project/audio/AudioContentFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAiB,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAa,MAAM,EAAY,MAAM,0BAA0B,CAAA;AACtE,OAAO,EAAC,GAAG,
|
|
1
|
+
{"version":3,"file":"AudioContentFactory.d.ts","sourceRoot":"","sources":["../../../src/project/audio/AudioContentFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAiB,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAa,MAAM,EAAY,MAAM,0BAA0B,CAAA;AACtE,OAAO,EAAC,GAAG,EAAuC,MAAM,kBAAkB,CAAA;AAC1E,OAAO,EACH,YAAY,EACZ,YAAY,EAEZ,cAAc,EAEd,QAAQ,EAEX,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAC,iBAAiB,EAAC,MAAM,uBAAuB,CAAA;AACvD,OAAO,EAAC,QAAQ,EAAC,MAAM,kBAAkB,CAAA;AAEzC,OAAO,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAA;AAEvD,yBAAiB,mBAAmB,CAAC;IACjC,KAAY,KAAK,GAAG;QAChB,QAAQ,EAAE,QAAQ,CAAA;QAClB,WAAW,EAAE,QAAQ,CAAA;QACrB,YAAY,EAAE,YAAY,CAAA;QAC1B,MAAM,EAAE,MAAM,CAAA;QACd,QAAQ,CAAC,EAAE,IAAI,GAAG,MAAM,CAAA;QACxB,WAAW,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAA;QAC/C,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IAED,KAAY,IAAI,GAAG,KAAK,GAAG;QAAE,KAAK,EAAE,GAAG,CAAA;KAAE,CAAA;IACzC,KAAY,MAAM,GAAG,KAAK,GAAG;QAAE,QAAQ,EAAE,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAE9D,KAAY,kBAAkB,GAAG;QAC7B,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;QACrC,YAAY,CAAC,EAAE,MAAM,CAAA;KACxB,GAAG,KAAK,CAAA;IAET,KAAY,mBAAmB,GAAG,EAA0C,GAAG,KAAK,CAAA;IAEpF,KAAY,iBAAiB,GAAG,EAA0C,GAAG,KAAK,CAAA;IAI3E,MAAM,yBAAyB,GAAI,OAAO,kBAAkB,GAAG,MAAM,KAAG,cAM9E,CAAA;IAEM,MAAM,0BAA0B,GAAI,OAAO,mBAAmB,GAAG,MAAM,KAAG,cAEhF,CAAA;IAEM,MAAM,wBAAwB,GAAI,OAAO,iBAAiB,GAAG,MAAM,KAAG,cAgB5E,CAAA;IAIM,MAAM,uBAAuB,GAAI,OAAO,kBAAkB,GAAG,IAAI,KAAG,YAM1E,CAAA;IAEM,MAAM,wBAAwB,GAAI,OAAO,mBAAmB,GAAG,IAAI,KAAG,YAG5E,CAAA;IAEM,MAAM,sBAAsB,GAAI,OAAO,iBAAiB,GAAG,IAAI,KAAG,YAcxE,CAAA;CA6DJ"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PPQN, TimeBase } from "@opendaw/lib-dsp";
|
|
2
2
|
import { ColorCodes, TrackType } from "@opendaw/studio-adapters";
|
|
3
|
-
import { isDefined, panic,
|
|
3
|
+
import { isDefined, panic, quantizeCeil, UUID } from "@opendaw/lib-std";
|
|
4
4
|
import { AudioClipBox, AudioPitchStretchBox, AudioRegionBox, AudioTimeStretchBox, ValueEventCollectionBox } from "@opendaw/studio-boxes";
|
|
5
5
|
import { TransientPlayMode } from "@opendaw/studio-enums";
|
|
6
6
|
import { AudioContentHelpers } from "./AudioContentHelpers";
|
|
@@ -68,7 +68,7 @@ export var AudioContentFactory;
|
|
|
68
68
|
return panic("Cannot create audio-region on non-audio track");
|
|
69
69
|
}
|
|
70
70
|
const { name, duration: durationInSeconds, bpm } = sample;
|
|
71
|
-
const durationInPPQN = props.duration ??
|
|
71
|
+
const durationInPPQN = props.duration ?? quantizeCeil(PPQN.secondsToPulses(durationInSeconds, bpm), PPQN.SemiQuaver);
|
|
72
72
|
if (isDefined(props.warpMarkers)) {
|
|
73
73
|
AudioContentHelpers.addWarpMarkers(boxGraph, playMode, props.warpMarkers);
|
|
74
74
|
}
|
|
@@ -97,7 +97,7 @@ export var AudioContentFactory;
|
|
|
97
97
|
return panic("Cannot create audio-region on non-audio track");
|
|
98
98
|
}
|
|
99
99
|
const { name, duration: durationInSeconds, bpm } = sample;
|
|
100
|
-
const durationInPPQN = props.duration ??
|
|
100
|
+
const durationInPPQN = props.duration ?? quantizeCeil(PPQN.secondsToPulses(durationInSeconds, bpm), PPQN.SemiQuaver);
|
|
101
101
|
if (isDefined(props.warpMarkers)) {
|
|
102
102
|
AudioContentHelpers.addWarpMarkers(boxGraph, playMode, props.warpMarkers);
|
|
103
103
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioContentModifier.d.ts","sourceRoot":"","sources":["../../../src/project/audio/AudioContentModifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,IAAI,EAAgC,MAAM,kBAAkB,CAAA;AAS/E,OAAO,EAAC,sBAAsB,EAAwB,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"AudioContentModifier.d.ts","sourceRoot":"","sources":["../../../src/project/audio/AudioContentModifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,IAAI,EAAgC,MAAM,kBAAkB,CAAA;AAS/E,OAAO,EAAC,sBAAsB,EAAwB,MAAM,0BAA0B,CAAA;AAKtF,yBAAiB,oBAAoB,CAAC;IAC3B,MAAM,cAAc,GAAU,UAAU,aAAa,CAAC,sBAAsB,CAAC,KAAG,OAAO,CAAC,IAAI,CAalG,CAAA;IAEM,MAAM,cAAc,GAAU,UAAU,aAAa,CAAC,sBAAsB,CAAC,KAAG,OAAO,CAAC,IAAI,CA4BlG,CAAA;IAEM,MAAM,aAAa,GAAU,UAAU,aAAa,CAAC,sBAAsB,CAAC,KAAG,OAAO,CAAC,IAAI,CA4CjG,CAAA;CA2BJ"}
|
|
@@ -4,6 +4,7 @@ import { AudioPitchStretchBox, AudioTimeStretchBox, TransientMarkerBox, WarpMark
|
|
|
4
4
|
import { AudioRegionBoxAdapter } from "@opendaw/studio-adapters";
|
|
5
5
|
import { AudioContentHelpers } from "./AudioContentHelpers";
|
|
6
6
|
import { Workers } from "../../Workers";
|
|
7
|
+
import { Pointers } from "@opendaw/studio-enums";
|
|
7
8
|
export var AudioContentModifier;
|
|
8
9
|
(function (AudioContentModifier) {
|
|
9
10
|
AudioContentModifier.toNotStretched = async (adapters) => {
|
|
@@ -13,6 +14,16 @@ export var AudioContentModifier;
|
|
|
13
14
|
}
|
|
14
15
|
return () => audioAdapters.forEach((adapter) => {
|
|
15
16
|
adapter.box.playMode.defer();
|
|
17
|
+
adapter.asPlayModeTimeStretch.ifSome(({ box }) => {
|
|
18
|
+
if (box.pointerHub.filter(Pointers.AudioPlayMode).length === 0) {
|
|
19
|
+
box.delete();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
adapter.asPlayModePitchStretch.ifSome(({ box }) => {
|
|
23
|
+
if (box.pointerHub.filter(Pointers.AudioPlayMode).length === 0) {
|
|
24
|
+
box.delete();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
16
27
|
switchTimeBaseToSeconds(adapter);
|
|
17
28
|
});
|
|
18
29
|
};
|
|
@@ -28,7 +39,8 @@ export var AudioContentModifier;
|
|
|
28
39
|
adapter.box.playMode.refer(pitchStretch);
|
|
29
40
|
if (optTimeStretch.nonEmpty()) {
|
|
30
41
|
const timeStretch = optTimeStretch.unwrap();
|
|
31
|
-
|
|
42
|
+
const numPointers = timeStretch.box.pointerHub.filter(Pointers.AudioPlayMode).length;
|
|
43
|
+
if (numPointers === 0) {
|
|
32
44
|
timeStretch.warpMarkers.asArray()
|
|
33
45
|
.forEach(({ box: { owner } }) => owner.refer(pitchStretch.warpMarkers));
|
|
34
46
|
timeStretch.box.delete();
|
|
@@ -69,7 +81,8 @@ export var AudioContentModifier;
|
|
|
69
81
|
adapter.box.playMode.refer(timeStretch);
|
|
70
82
|
if (optPitchStretch.nonEmpty()) {
|
|
71
83
|
const pitchStretch = optPitchStretch.unwrap();
|
|
72
|
-
|
|
84
|
+
const numPointers = pitchStretch.box.pointerHub.filter(Pointers.AudioPlayMode).length;
|
|
85
|
+
if (numPointers === 0) {
|
|
73
86
|
pitchStretch.warpMarkers.asArray()
|
|
74
87
|
.forEach(({ box: { owner } }) => owner.refer(timeStretch.warpMarkers));
|
|
75
88
|
pitchStretch.box.delete();
|
|
@@ -105,7 +118,6 @@ export var AudioContentModifier;
|
|
|
105
118
|
box.duration.setValue(file.endInSeconds);
|
|
106
119
|
box.accept({
|
|
107
120
|
visitAudioRegionBox: (box) => {
|
|
108
|
-
console.debug("RESET");
|
|
109
121
|
box.loopOffset.setValue(0);
|
|
110
122
|
box.loopDuration.setValue(file.endInSeconds);
|
|
111
123
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioFileBoxFactory.d.ts","sourceRoot":"","sources":["../../../src/project/audio/AudioFileBoxFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAqB,MAAM,uBAAuB,CAAA;AACtE,OAAO,EAAS,QAAQ,EAAE,IAAI,EAAC,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAC,SAAS,EAAE,iBAAiB,EAAC,MAAM,kBAAkB,CAAA;AAC7D,OAAO,EAAC,QAAQ,EAAC,MAAM,kBAAkB,CAAA;AAEzC,yBAAiB,mBAAmB,CAAC;IAO1B,MAAM,cAAc,GAAU,mBAAmB,iBAAiB,EACpC,UAAU,QAAQ,EAClB,WAAW,SAAS,EACpB,MAAM,IAAI,CAAC,KAAK,EAChB,MAAM,MAAM,KAAG,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"AudioFileBoxFactory.d.ts","sourceRoot":"","sources":["../../../src/project/audio/AudioFileBoxFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAqB,MAAM,uBAAuB,CAAA;AACtE,OAAO,EAAS,QAAQ,EAAE,IAAI,EAAC,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAC,SAAS,EAAE,iBAAiB,EAAC,MAAM,kBAAkB,CAAA;AAC7D,OAAO,EAAC,QAAQ,EAAC,MAAM,kBAAkB,CAAA;AAEzC,yBAAiB,mBAAmB,CAAC;IAO1B,MAAM,cAAc,GAAU,mBAAmB,iBAAiB,EACpC,UAAU,QAAQ,EAClB,WAAW,SAAS,EACpB,MAAM,IAAI,CAAC,KAAK,EAChB,MAAM,MAAM,KAAG,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CA8BjF,CAAA;CACJ"}
|
|
@@ -23,6 +23,11 @@ export var AudioFileBoxFactory;
|
|
|
23
23
|
const transients = await transientProtocol.detect(audioData);
|
|
24
24
|
const durationInSeconds = audioData.numberOfFrames / audioData.sampleRate;
|
|
25
25
|
return () => {
|
|
26
|
+
// Re-check in case another drop created it between createModifier and now
|
|
27
|
+
const existingBox = boxGraph.findBox(uuid);
|
|
28
|
+
if (existingBox.nonEmpty()) {
|
|
29
|
+
return existingBox.unwrap();
|
|
30
|
+
}
|
|
26
31
|
const audioFileBox = AudioFileBox.create(boxGraph, uuid, box => {
|
|
27
32
|
box.fileName.setValue(name);
|
|
28
33
|
box.startInSeconds.setValue(0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultSampleLoader.d.ts","sourceRoot":"","sources":["../../src/samples/DefaultSampleLoader.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,QAAQ,EACR,MAAM,EAEN,YAAY,EAEZ,IAAI,EACP,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAC,KAAK,EAAc,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAC,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAC,MAAM,0BAA0B,CAAA;AAExF,OAAO,EAAC,0BAA0B,EAAC,MAAM,8BAA8B,CAAA;AAEvE,OAAO,EAAC,SAAS,EAAC,MAAM,kBAAkB,CAAA;AAE1C,qBAAa,mBAAoB,YAAW,YAAY;;gBAYxC,OAAO,EAAE,0BAA0B,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK;IAQjE,UAAU,IAAI,IAAI;IASlB,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GAAG,YAAY;IAQ9D,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK,CAAoB;IAC1C,IAAI,IAAI,IAAI,MAAM,CAAC,SAAS,CAAC,CAAoB;IACjD,IAAI,IAAI,IAAI,MAAM,CAAC,cAAc,CAAC,CAAoB;IACtD,IAAI,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,CAAqB;IAC/C,IAAI,KAAK,IAAI,iBAAiB,CAAqB;IAEnD,QAAQ,IAAI,MAAM;
|
|
1
|
+
{"version":3,"file":"DefaultSampleLoader.d.ts","sourceRoot":"","sources":["../../src/samples/DefaultSampleLoader.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,QAAQ,EACR,MAAM,EAEN,YAAY,EAEZ,IAAI,EACP,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAC,KAAK,EAAc,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAC,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAC,MAAM,0BAA0B,CAAA;AAExF,OAAO,EAAC,0BAA0B,EAAC,MAAM,8BAA8B,CAAA;AAEvE,OAAO,EAAC,SAAS,EAAC,MAAM,kBAAkB,CAAA;AAE1C,qBAAa,mBAAoB,YAAW,YAAY;;gBAYxC,OAAO,EAAE,0BAA0B,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK;IAQjE,UAAU,IAAI,IAAI;IASlB,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GAAG,YAAY;IAQ9D,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK,CAAoB;IAC1C,IAAI,IAAI,IAAI,MAAM,CAAC,SAAS,CAAC,CAAoB;IACjD,IAAI,IAAI,IAAI,MAAM,CAAC,cAAc,CAAC,CAAoB;IACtD,IAAI,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,CAAqB;IAC/C,IAAI,KAAK,IAAI,iBAAiB,CAAqB;IAEnD,QAAQ,IAAI,MAAM;CAuErB"}
|
|
@@ -75,8 +75,10 @@ export class DefaultSampleLoader {
|
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
if (fetchResult.status === "rejected") {
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
const error = fetchResult.error;
|
|
79
|
+
console.warn(error);
|
|
80
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
81
|
+
this.#setState({ type: "error", reason });
|
|
80
82
|
return;
|
|
81
83
|
}
|
|
82
84
|
const [audio, meta] = fetchResult.value;
|
|
@@ -98,8 +100,10 @@ export class DefaultSampleLoader {
|
|
|
98
100
|
this.#setState({ type: "loaded" });
|
|
99
101
|
}
|
|
100
102
|
else {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
const error = storeResult.error;
|
|
104
|
+
console.warn(error);
|
|
105
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
106
|
+
this.#setState({ type: "error", reason });
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultSampleLoaderManager.d.ts","sourceRoot":"","sources":["../../src/samples/DefaultSampleLoaderManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,
|
|
1
|
+
{"version":3,"file":"DefaultSampleLoaderManager.d.ts","sourceRoot":"","sources":["../../src/samples/DefaultSampleLoaderManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAA2B,IAAI,EAAC,MAAM,kBAAkB,CAAA;AAExE,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAC,YAAY,EAAE,mBAAmB,EAAE,cAAc,EAAC,MAAM,0BAA0B,CAAA;AAC1F,OAAO,EAAC,SAAS,EAAC,MAAM,kBAAkB,CAAA;AAE1C,qBAAa,0BAA2B,YAAW,mBAAmB,EAAE,cAAc;;gBAItE,QAAQ,EAAE,cAAc;IAKpC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IAIzF,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK;IACvB,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK;IAE3B,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAElC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,GAAG,YAAY;IAIrC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC;CAe3D"}
|
|
@@ -19,11 +19,14 @@ export class DefaultSampleLoaderManager {
|
|
|
19
19
|
async getAudioData(uuid) {
|
|
20
20
|
const { promise, resolve, reject } = Promise.withResolvers();
|
|
21
21
|
const loader = this.getOrCreate(uuid);
|
|
22
|
-
|
|
22
|
+
let subscription;
|
|
23
|
+
subscription = loader.subscribe(state => {
|
|
23
24
|
if (state.type === "error") {
|
|
24
|
-
|
|
25
|
+
queueMicrotask(() => subscription.terminate());
|
|
26
|
+
reject(new Error(state.reason));
|
|
25
27
|
}
|
|
26
28
|
else if (loader.data.nonEmpty()) {
|
|
29
|
+
queueMicrotask(() => subscription.terminate());
|
|
27
30
|
resolve(loader.data.unwrap());
|
|
28
31
|
}
|
|
29
32
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultSoundfontLoader.d.ts","sourceRoot":"","sources":["../../src/soundfont/DefaultSoundfontLoader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,QAAQ,EAAE,MAAM,EAAY,YAAY,EAAc,IAAI,EAAC,MAAM,kBAAkB,CAAA;AAErG,OAAO,EAAC,eAAe,EAAE,oBAAoB,EAAE,iBAAiB,EAAC,MAAM,0BAA0B,CAAA;AACjG,OAAO,EAAC,6BAA6B,EAAC,MAAM,iCAAiC,CAAA;AAE7E,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,YAAY,CAAA;AAG1C,qBAAa,sBAAuB,YAAW,eAAe;;
|
|
1
|
+
{"version":3,"file":"DefaultSoundfontLoader.d.ts","sourceRoot":"","sources":["../../src/soundfont/DefaultSoundfontLoader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,QAAQ,EAAE,MAAM,EAAY,YAAY,EAAc,IAAI,EAAC,MAAM,kBAAkB,CAAA;AAErG,OAAO,EAAC,eAAe,EAAE,oBAAoB,EAAE,iBAAiB,EAAC,MAAM,0BAA0B,CAAA;AACjG,OAAO,EAAC,6BAA6B,EAAC,MAAM,iCAAiC,CAAA;AAE7E,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,YAAY,CAAA;AAG1C,qBAAa,sBAAuB,YAAW,eAAe;;gBAU9C,OAAO,EAAE,6BAA6B,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK;IAQpE,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,oBAAoB,CAAC,GAAG,YAAY;IAQjE,UAAU,IAAI,IAAI;IAOlB,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK,CAAoB;IAC1C,IAAI,SAAS,IAAI,MAAM,CAAC,UAAU,CAAC,CAAyB;IAC5D,IAAI,IAAI,IAAI,MAAM,CAAC,iBAAiB,CAAC,CAAoB;IACzD,IAAI,KAAK,IAAI,oBAAoB,CAAqB;IAEtD,QAAQ,IAAI,MAAM;CAkDrB"}
|
|
@@ -6,7 +6,6 @@ export class DefaultSoundfontLoader {
|
|
|
6
6
|
#manager;
|
|
7
7
|
#uuid;
|
|
8
8
|
#notifier;
|
|
9
|
-
#soundFont2 = Promises.memoizeAsync(() => ExternalLib.SoundFont2());
|
|
10
9
|
#meta = Option.None;
|
|
11
10
|
#soundfont = Option.None;
|
|
12
11
|
#state = { type: "progress", progress: 0.0 };
|
|
@@ -40,7 +39,10 @@ export class DefaultSoundfontLoader {
|
|
|
40
39
|
}
|
|
41
40
|
#get() {
|
|
42
41
|
SoundfontStorage.get().load(this.#uuid).then(async ([file, meta]) => {
|
|
43
|
-
|
|
42
|
+
const { status, value: SoundFont2, error } = await ExternalLib.SoundFont2();
|
|
43
|
+
if (status === "rejected")
|
|
44
|
+
return console.warn(error);
|
|
45
|
+
this.#soundfont = Option.wrap(new SoundFont2(new Uint8Array(file)));
|
|
44
46
|
this.#meta = Option.wrap(meta);
|
|
45
47
|
this.#setState({ type: "loaded" });
|
|
46
48
|
}, (error) => {
|
|
@@ -57,24 +59,27 @@ export class DefaultSoundfontLoader {
|
|
|
57
59
|
const fetchProgress = progress => this.#setState({ type: "progress", progress });
|
|
58
60
|
const fetchResult = await Promises.tryCatch(this.#manager.fetch(this.#uuid, fetchProgress));
|
|
59
61
|
if (fetchResult.status === "rejected") {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
const error = fetchResult.error;
|
|
63
|
+
console.warn(error);
|
|
64
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
65
|
+
this.#setState({ type: "error", reason });
|
|
62
66
|
return;
|
|
63
67
|
}
|
|
64
68
|
const [file, meta] = fetchResult.value;
|
|
65
69
|
const storeResult = await Promises.tryCatch(SoundfontStorage.get().save({ uuid: this.#uuid, file, meta }));
|
|
66
70
|
if (storeResult.status === "resolved") {
|
|
67
|
-
|
|
71
|
+
const { status, value: SoundFont2, error } = await ExternalLib.SoundFont2();
|
|
72
|
+
if (status === "rejected")
|
|
73
|
+
return console.warn(error);
|
|
74
|
+
this.#soundfont = Option.wrap(new SoundFont2(new Uint8Array(file)));
|
|
68
75
|
this.#meta = Option.wrap(meta);
|
|
69
76
|
this.#setState({ type: "loaded" });
|
|
70
77
|
}
|
|
71
78
|
else {
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
const error = storeResult.error;
|
|
80
|
+
console.warn(error);
|
|
81
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
82
|
+
this.#setState({ type: "error", reason });
|
|
74
83
|
}
|
|
75
84
|
}
|
|
76
|
-
async #createSoundFont2(buffer) {
|
|
77
|
-
const SoundFont2 = await this.#soundFont2();
|
|
78
|
-
return new SoundFont2(new Uint8Array(buffer));
|
|
79
|
-
}
|
|
80
85
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SoundfontService.d.ts","sourceRoot":"","sources":["../../src/soundfont/SoundfontService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,EAAE,MAAM,EAAS,SAAS,EAAwB,MAAM,kBAAkB,CAAA;AAC/F,OAAO,EAAC,GAAG,EAAC,MAAM,kBAAkB,CAAA;AAGpC,OAAO,EAAC,SAAS,EAAoB,MAAM,0BAA0B,CAAA;AAIrE,OAAO,EAAC,YAAY,EAAC,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"SoundfontService.d.ts","sourceRoot":"","sources":["../../src/soundfont/SoundfontService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,EAAE,MAAM,EAAS,SAAS,EAAwB,MAAM,kBAAkB,CAAA;AAC/F,OAAO,EAAC,GAAG,EAAC,MAAM,kBAAkB,CAAA;AAGpC,OAAO,EAAC,SAAS,EAAoB,MAAM,0BAA0B,CAAA;AAIrE,OAAO,EAAC,YAAY,EAAC,MAAM,iBAAiB,CAAA;AAG5C,qBAAa,gBAAiB,SAAQ,YAAY,CAAC,SAAS,CAAC;;IACzD,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAe;IACpD,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAc;IACrD,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,CAAmB;IACzD,SAAS,CAAC,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAuC;gBAKlF,QAAQ,EAAE,SAAS,CAAC,SAAS,CAAC;IAY1C,IAAI,KAAK,IAAI,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAqB;IAClE,IAAI,MAAM,IAAI,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAsB;IAE9D,UAAU,CAAC,EAAC,IAAI,EAAE,WAAW,EAAC,EAAE,YAAY,CAAC,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;cA4ClE,eAAe,IAAI,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;CAKvE"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Arrays, Option, panic, RuntimeNotifier, UUID } from "@opendaw/lib-std";
|
|
2
|
-
import {
|
|
2
|
+
import { Wait } from "@opendaw/lib-runtime";
|
|
3
3
|
import { SoundfontFileBox } from "@opendaw/studio-boxes";
|
|
4
4
|
import { SoundfontStorage } from "./SoundfontStorage";
|
|
5
5
|
import { FilePickerAcceptTypes } from "../FilePickerAcceptTypes";
|
|
@@ -44,12 +44,13 @@ export class SoundfontService extends AssetService {
|
|
|
44
44
|
uuid ??= await UUID.sha256(arrayBuffer);
|
|
45
45
|
console.timeEnd("UUID.sha256");
|
|
46
46
|
console.time("SoundFont2");
|
|
47
|
-
const { status, value:
|
|
47
|
+
const { status, value: SoundFont2, error } = await ExternalLib.SoundFont2();
|
|
48
48
|
console.timeEnd("SoundFont2");
|
|
49
49
|
if (status === "rejected") {
|
|
50
50
|
updater.terminate();
|
|
51
51
|
return panic(error);
|
|
52
52
|
}
|
|
53
|
+
const soundFont2 = new SoundFont2(new Uint8Array(arrayBuffer));
|
|
53
54
|
const meta = {
|
|
54
55
|
name: soundFont2.metaData.name,
|
|
55
56
|
size: arrayBuffer.byteLength,
|
|
@@ -72,8 +73,4 @@ export class SoundfontService extends AssetService {
|
|
|
72
73
|
const local = await SoundfontStorage.get().list();
|
|
73
74
|
return Arrays.merge(stock, local, (a, b) => a.uuid === b.uuid);
|
|
74
75
|
}
|
|
75
|
-
async #createSoundFont2(buffer) {
|
|
76
|
-
const SoundFont2 = await ExternalLib.SoundFont2();
|
|
77
|
-
return new SoundFont2(new Uint8Array(buffer));
|
|
78
|
-
}
|
|
79
76
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RegionClipResolver.d.ts","sourceRoot":"","sources":["../../src/ui/RegionClipResolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,IAAI,EAAE,GAAG,EAAa,MAAM,kBAAkB,CAAA;AACzE,OAAO,EAAC,KAAK,EAAmB,IAAI,EAAW,MAAM,kBAAkB,CAAA;AACvE,OAAO,EACH,mBAAmB,EAGnB,eAAe,EAGlB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAC,sBAAsB,EAAC,MAAM,0BAA0B,CAAA;AAE/D,MAAM,MAAM,QAAQ,GAAG;IACnB,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,mBAAmB,CAAA;CAC9B,GAAG;IACA,IAAI,EAAE,UAAU,CAAA;IAChB,MAAM,EAAE,mBAAmB,CAAA;IAC3B,KAAK,EAAE,IAAI,CAAA;IACX,GAAG,EAAE,IAAI,CAAA;CACZ,GAAG;IACA,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,EAAE,mBAAmB,CAAA;IAC3B,QAAQ,EAAE,IAAI,CAAA;CACjB,GAAG;IACA,IAAI,EAAE,UAAU,CAAA;IAChB,MAAM,EAAE,mBAAmB,CAAA;IAC3B,QAAQ,EAAE,IAAI,CAAA;CACjB,CAAA;AAED,UAAU,IAAK,SAAQ,KAAK;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAC;AAO7C,qBAAa,kBAAkB;;IAC3B,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,eAAe,CAAC,EACtC,QAAQ,EAAE,aAAa,CAAC,mBAAmB,CAAC,EAC5C,QAAQ,EAAE,sBAAsB,EAChC,UAAU,GAAE,GAAO,GAAG,IAAI;IAY/C,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAG,IAAI;IAO9E,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,aAAa,CAAC,eAAe,CAAC,GAAG,IAAI;IAInE,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;IAuBlD,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC;gBAuC5D,QAAQ,EAAE,sBAAsB,EAAE,MAAM,EAAE,eAAe;IAMrE,OAAO,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI;IAK1C,YAAY,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"RegionClipResolver.d.ts","sourceRoot":"","sources":["../../src/ui/RegionClipResolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,IAAI,EAAE,GAAG,EAAa,MAAM,kBAAkB,CAAA;AACzE,OAAO,EAAC,KAAK,EAAmB,IAAI,EAAW,MAAM,kBAAkB,CAAA;AACvE,OAAO,EACH,mBAAmB,EAGnB,eAAe,EAGlB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAAC,sBAAsB,EAAC,MAAM,0BAA0B,CAAA;AAE/D,MAAM,MAAM,QAAQ,GAAG;IACnB,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,mBAAmB,CAAA;CAC9B,GAAG;IACA,IAAI,EAAE,UAAU,CAAA;IAChB,MAAM,EAAE,mBAAmB,CAAA;IAC3B,KAAK,EAAE,IAAI,CAAA;IACX,GAAG,EAAE,IAAI,CAAA;CACZ,GAAG;IACA,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,EAAE,mBAAmB,CAAA;IAC3B,QAAQ,EAAE,IAAI,CAAA;CACjB,GAAG;IACA,IAAI,EAAE,UAAU,CAAA;IAChB,MAAM,EAAE,mBAAmB,CAAA;IAC3B,QAAQ,EAAE,IAAI,CAAA;CACjB,CAAA;AAED,UAAU,IAAK,SAAQ,KAAK;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAC;AAO7C,qBAAa,kBAAkB;;IAC3B,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,aAAa,CAAC,eAAe,CAAC,EACtC,QAAQ,EAAE,aAAa,CAAC,mBAAmB,CAAC,EAC5C,QAAQ,EAAE,sBAAsB,EAChC,UAAU,GAAE,GAAO,GAAG,IAAI;IAY/C,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAG,IAAI;IAO9E,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,aAAa,CAAC,eAAe,CAAC,GAAG,IAAI;IAInE,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;IAuBlD,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC;gBAuC5D,QAAQ,EAAE,sBAAsB,EAAE,MAAM,EAAE,eAAe;IAMrE,OAAO,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI;IAK1C,YAAY,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAG,IAAI;CA+ErD"}
|
|
@@ -106,16 +106,21 @@ export class RegionClipResolver {
|
|
|
106
106
|
#createTasksFromMasks(masks) {
|
|
107
107
|
const tasks = [];
|
|
108
108
|
masks.forEach(({ position, complete }) => {
|
|
109
|
-
|
|
109
|
+
// Iterate from 0 to find all regions that OVERLAP with [position, complete],
|
|
110
|
+
// not just regions that START within that range
|
|
111
|
+
for (const region of this.#ground.regions.collection.iterateRange(0, complete)) {
|
|
112
|
+
if (region.position >= complete) {
|
|
113
|
+
break;
|
|
114
|
+
} // past the mask, done
|
|
115
|
+
if (region.complete <= position) {
|
|
116
|
+
continue;
|
|
117
|
+
} // ends before mask, skip
|
|
110
118
|
if (region.isSelected && !this.#strategy.showOrigin()) {
|
|
111
119
|
continue;
|
|
112
120
|
}
|
|
113
121
|
else if (region.duration <= 0) {
|
|
114
122
|
return panic(`Invalid duration(${region.duration})`);
|
|
115
123
|
}
|
|
116
|
-
else if (region.complete <= position || region.position >= complete) {
|
|
117
|
-
return panic("Not overlapping");
|
|
118
|
-
}
|
|
119
124
|
const positionIn = region.position >= position;
|
|
120
125
|
const completeIn = region.complete <= complete;
|
|
121
126
|
if (positionIn && completeIn) {
|
|
@@ -154,9 +159,13 @@ export class RegionClipResolver {
|
|
|
154
159
|
case "start":
|
|
155
160
|
if (UnionAdapterTypes.isLoopableRegion(region)) {
|
|
156
161
|
const delta = task.position - region.position;
|
|
162
|
+
// Capture old values BEFORE changing position (they depend on position for TimeBase.Seconds)
|
|
163
|
+
const oldDuration = region.duration;
|
|
164
|
+
const oldLoopOffset = region.loopOffset;
|
|
165
|
+
const oldLoopDuration = region.loopDuration;
|
|
157
166
|
region.position = region.position + delta;
|
|
158
|
-
region.duration =
|
|
159
|
-
region.loopOffset = mod(
|
|
167
|
+
region.duration = oldDuration - delta;
|
|
168
|
+
region.loopOffset = mod(oldLoopOffset + delta, oldLoopDuration);
|
|
160
169
|
}
|
|
161
170
|
else {
|
|
162
171
|
return panic("Not yet implemented");
|