@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
|
@@ -1,19 +1,53 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { asDefined, asInstanceOf, EmptyExec, Errors, isNotNull, Option, RuntimeNotifier, Terminator, UUID } from "@opendaw/lib-std";
|
|
2
2
|
import { Address } from "@opendaw/lib-box";
|
|
3
3
|
import { MidiData } from "@opendaw/lib-midi";
|
|
4
4
|
import { MidiDevices } from "./MidiDevices";
|
|
5
5
|
import { AnimationFrame } from "@opendaw/lib-dom";
|
|
6
|
+
import { MIDIControllerBox } from "@opendaw/studio-boxes";
|
|
7
|
+
// will be part of MIDI preferences. Should not filter for the device-id
|
|
8
|
+
// because it is different for the same hardware in different browsers.
|
|
9
|
+
const respectDeviceID = false;
|
|
6
10
|
export class MIDILearning {
|
|
7
11
|
#terminator = new Terminator();
|
|
8
12
|
#project;
|
|
9
13
|
#connections;
|
|
14
|
+
#optMIDIContollers = Option.None;
|
|
15
|
+
#optFieldSubscription = Option.None;
|
|
10
16
|
constructor(project) {
|
|
11
17
|
this.#project = project;
|
|
12
|
-
this.#connections = Address.newSet(connection => connection.address);
|
|
18
|
+
this.#connections = Address.newSet(connection => connection.box.address);
|
|
19
|
+
}
|
|
20
|
+
followUser(field) {
|
|
21
|
+
this.#killAllConnections();
|
|
22
|
+
this.#optFieldSubscription.ifSome(subscription => subscription.terminate());
|
|
23
|
+
this.#optFieldSubscription = Option.None;
|
|
24
|
+
this.#optMIDIContollers = Option.wrap(field);
|
|
25
|
+
this.#optFieldSubscription = Option.wrap(field.pointerHub.catchupAndSubscribe({
|
|
26
|
+
onAdded: ({ box: anyBox }) => {
|
|
27
|
+
if (MidiDevices.get().isEmpty() && MidiDevices.canRequestMidiAccess()) {
|
|
28
|
+
MidiDevices.requestPermission().then(EmptyExec, EmptyExec);
|
|
29
|
+
}
|
|
30
|
+
const box = asInstanceOf(anyBox, MIDIControllerBox);
|
|
31
|
+
const { subscription, handleEvent } = this.#registerMIDIControllerBox(box);
|
|
32
|
+
this.#connections.add({ box, subscription, handleEvent });
|
|
33
|
+
},
|
|
34
|
+
onRemoved: ({ box: { address } }) => this.#connections.removeByKey(address).subscription.terminate()
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
hasMidiConnection(address) {
|
|
38
|
+
return this.#findConnectionByParameterAddress(address).nonEmpty();
|
|
39
|
+
}
|
|
40
|
+
forgetMidiConnection(address) {
|
|
41
|
+
const connection = this.#findConnectionByParameterAddress(address).unwrap("No connection to forget");
|
|
42
|
+
this.#project.editing.modify(() => asDefined(connection).box.delete());
|
|
13
43
|
}
|
|
14
|
-
hasMidiConnection(address) { return this.#connections.hasKey(address); }
|
|
15
|
-
forgetMidiConnection(address) { this.#connections.removeByKey(address).terminate(); }
|
|
16
44
|
async learnMIDIControls(field) {
|
|
45
|
+
if (this.#optMIDIContollers.isEmpty()) {
|
|
46
|
+
return RuntimeNotifier.info({
|
|
47
|
+
headline: "Learn Midi Controller...",
|
|
48
|
+
message: "No user accepting midi controls."
|
|
49
|
+
});
|
|
50
|
+
}
|
|
17
51
|
if (!MidiDevices.canRequestMidiAccess()) {
|
|
18
52
|
return;
|
|
19
53
|
}
|
|
@@ -25,83 +59,81 @@ export class MIDILearning {
|
|
|
25
59
|
if (data === null) {
|
|
26
60
|
return;
|
|
27
61
|
}
|
|
62
|
+
const deviceId = event.target instanceof MIDIInput ? event.target.id : "";
|
|
28
63
|
if (MidiData.isController(data)) {
|
|
29
64
|
learnLifecycle.terminate();
|
|
30
65
|
abortController.abort(Errors.AbortError);
|
|
31
|
-
|
|
66
|
+
const midiControllersField = this.#optMIDIContollers.unwrap();
|
|
67
|
+
const { editing } = this.#project;
|
|
68
|
+
const optBox = editing.modify(() => MIDIControllerBox.create(this.#project.boxGraph, UUID.generate(), box => {
|
|
69
|
+
box.controllers.refer(midiControllersField);
|
|
70
|
+
box.parameter.refer(field);
|
|
71
|
+
box.deviceId.setValue(deviceId);
|
|
72
|
+
box.deviceChannel.setValue(MidiData.readChannel(data));
|
|
73
|
+
box.controlId.setValue(MidiData.readParam1(data));
|
|
74
|
+
}));
|
|
75
|
+
this.#connections.get(optBox.unwrap("Could not create MIDIControllerBox").address).handleEvent(event);
|
|
32
76
|
}
|
|
33
77
|
}));
|
|
34
78
|
return RuntimeNotifier.info({
|
|
35
|
-
headline: "Learn Midi
|
|
36
|
-
message: "
|
|
79
|
+
headline: "Learn Midi Controller...",
|
|
80
|
+
message: "Turn a controller on your midi-device...",
|
|
37
81
|
okText: "Cancel",
|
|
38
82
|
abortSignal: abortController.signal
|
|
39
83
|
}).then(() => learnLifecycle.terminate(), Errors.CatchAbort);
|
|
40
84
|
}
|
|
41
|
-
toJSON() {
|
|
42
|
-
return this.#connections.values().map(connection => connection.toJSON());
|
|
43
|
-
}
|
|
44
85
|
terminate() {
|
|
45
86
|
this.#killAllConnections();
|
|
46
87
|
this.#terminator.terminate();
|
|
47
88
|
}
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
observer(event);
|
|
53
|
-
}
|
|
54
|
-
const subscription = MidiDevices.subscribeMessageEvents(observer, channel);
|
|
55
|
-
this.#connections.add({
|
|
56
|
-
address: field.address,
|
|
57
|
-
toJSON: () => ({
|
|
58
|
-
type: "control",
|
|
59
|
-
address: field.address.toJSON(),
|
|
60
|
-
channel,
|
|
61
|
-
controlId
|
|
62
|
-
}),
|
|
63
|
-
label: () => this.#project.parameterFieldAdapters.get(field.address).name,
|
|
64
|
-
terminate: () => {
|
|
65
|
-
terminate();
|
|
66
|
-
subscription.terminate();
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
#killAllConnections() {
|
|
71
|
-
this.#connections.forEach(({ terminate }) => terminate());
|
|
72
|
-
this.#connections.clear();
|
|
73
|
-
}
|
|
74
|
-
#createMidiControlObserver(project, adapter, controlId) {
|
|
89
|
+
#registerMIDIControllerBox({ parameter: { targetAddress }, controlId, deviceId, deviceChannel }) {
|
|
90
|
+
const address = targetAddress.unwrap("No parameter address");
|
|
91
|
+
const adapter = this.#project.parameterFieldAdapters.get(address);
|
|
92
|
+
const { editing } = this.#project;
|
|
75
93
|
const registration = adapter.registerMidiControl();
|
|
76
94
|
let pendingValue = null;
|
|
77
|
-
const update = (value) =>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
const update = (value) => editing.modify(() => adapter.setUnitValue(value), false);
|
|
96
|
+
const handleEvent = (event) => {
|
|
97
|
+
const data = event.data;
|
|
98
|
+
if (data === null) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const id = event.target instanceof MIDIInput ? event.target.id : "";
|
|
102
|
+
if (MidiData.isController(data)
|
|
103
|
+
&& MidiData.readParam1(data) === controlId.getValue() && (!respectDeviceID || id === deviceId.getValue())) {
|
|
104
|
+
const value = MidiData.asValue(data);
|
|
105
|
+
if (pendingValue === null) {
|
|
106
|
+
update(value);
|
|
107
|
+
pendingValue = value;
|
|
108
|
+
AnimationFrame.once(() => {
|
|
109
|
+
if (isNotNull(pendingValue)) {
|
|
110
|
+
update(pendingValue);
|
|
111
|
+
pendingValue = null;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
83
114
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (pendingValue === null) {
|
|
87
|
-
update(value);
|
|
88
|
-
pendingValue = value;
|
|
89
|
-
AnimationFrame.once(() => {
|
|
90
|
-
if (isNotNull(pendingValue)) {
|
|
91
|
-
update(pendingValue);
|
|
92
|
-
pendingValue = null;
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
pendingValue = value;
|
|
98
|
-
}
|
|
115
|
+
else {
|
|
116
|
+
pendingValue = value;
|
|
99
117
|
}
|
|
100
|
-
},
|
|
101
|
-
terminate: () => {
|
|
102
|
-
pendingValue = null;
|
|
103
|
-
registration.terminate();
|
|
104
118
|
}
|
|
105
119
|
};
|
|
120
|
+
const channel = deviceChannel.getValue() === -1 ? undefined : deviceChannel.getValue();
|
|
121
|
+
const subscription = MidiDevices.subscribeMessageEvents(handleEvent, channel);
|
|
122
|
+
return {
|
|
123
|
+
subscription: {
|
|
124
|
+
terminate: () => {
|
|
125
|
+
pendingValue = null;
|
|
126
|
+
subscription.terminate();
|
|
127
|
+
registration.terminate();
|
|
128
|
+
}
|
|
129
|
+
}, handleEvent
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
#killAllConnections() {
|
|
133
|
+
this.#connections.forEach(({ subscription }) => subscription.terminate());
|
|
134
|
+
this.#connections.clear();
|
|
135
|
+
}
|
|
136
|
+
#findConnectionByParameterAddress(address) {
|
|
137
|
+
return Option.wrap(this.#connections.values().find(({ box }) => box.parameter.targetAddress.unwrap() === address));
|
|
106
138
|
}
|
|
107
139
|
}
|