@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.
Files changed (70) hide show
  1. package/dist/EffectFactories.d.ts +0 -2
  2. package/dist/EffectFactories.d.ts.map +1 -1
  3. package/dist/EffectFactories.js +1 -1
  4. package/dist/Engine.d.ts +2 -1
  5. package/dist/Engine.d.ts.map +1 -1
  6. package/dist/EngineFacade.d.ts +2 -1
  7. package/dist/EngineFacade.d.ts.map +1 -1
  8. package/dist/EngineFacade.js +3 -1
  9. package/dist/EngineWorklet.d.ts +2 -1
  10. package/dist/EngineWorklet.d.ts.map +1 -1
  11. package/dist/EngineWorklet.js +5 -2
  12. package/dist/ExternalLib.d.ts +3 -2
  13. package/dist/ExternalLib.d.ts.map +1 -1
  14. package/dist/ExternalLib.js +2 -2
  15. package/dist/Preferences.d.ts +6 -0
  16. package/dist/Preferences.d.ts.map +1 -1
  17. package/dist/Preferences.js +5 -2
  18. package/dist/Storage.d.ts.map +1 -1
  19. package/dist/Storage.js +14 -5
  20. package/dist/capture/Capture.d.ts +4 -1
  21. package/dist/capture/Capture.d.ts.map +1 -1
  22. package/dist/capture/Capture.js +4 -0
  23. package/dist/capture/CaptureDevices.js +1 -1
  24. package/dist/capture/RecordAudio.d.ts.map +1 -1
  25. package/dist/capture/RecordAudio.js +20 -10
  26. package/dist/capture/RecordMidi.d.ts.map +1 -1
  27. package/dist/capture/RecordMidi.js +1 -0
  28. package/dist/capture/Recording.d.ts.map +1 -1
  29. package/dist/capture/Recording.js +1 -0
  30. package/dist/dawproject/BuiltinDevices.d.ts +1 -1
  31. package/dist/dawproject/DawProject.d.ts.map +1 -1
  32. package/dist/dawproject/DawProject.js +17 -3
  33. package/dist/midi/MIDILearning.d.ts +4 -16
  34. package/dist/midi/MIDILearning.d.ts.map +1 -1
  35. package/dist/midi/MIDILearning.js +94 -62
  36. package/dist/processors.js +17 -17
  37. package/dist/processors.js.map +4 -4
  38. package/dist/project/Project.d.ts +2 -2
  39. package/dist/project/Project.d.ts.map +1 -1
  40. package/dist/project/Project.js +16 -9
  41. package/dist/project/ProjectApi.js +2 -2
  42. package/dist/project/ProjectBundle.d.ts.map +1 -1
  43. package/dist/project/ProjectBundle.js +21 -6
  44. package/dist/project/ProjectMigration.d.ts.map +1 -1
  45. package/dist/project/ProjectMigration.js +20 -4
  46. package/dist/project/ProjectValidation.d.ts.map +1 -1
  47. package/dist/project/ProjectValidation.js +15 -13
  48. package/dist/project/audio/AudioContentFactory.d.ts.map +1 -1
  49. package/dist/project/audio/AudioContentFactory.js +3 -3
  50. package/dist/project/audio/AudioContentModifier.d.ts.map +1 -1
  51. package/dist/project/audio/AudioContentModifier.js +15 -3
  52. package/dist/project/audio/AudioFileBoxFactory.d.ts.map +1 -1
  53. package/dist/project/audio/AudioFileBoxFactory.js +5 -0
  54. package/dist/samples/DefaultSampleLoader.d.ts.map +1 -1
  55. package/dist/samples/DefaultSampleLoader.js +8 -4
  56. package/dist/samples/DefaultSampleLoaderManager.d.ts.map +1 -1
  57. package/dist/samples/DefaultSampleLoaderManager.js +5 -2
  58. package/dist/soundfont/DefaultSoundfontLoader.d.ts.map +1 -1
  59. package/dist/soundfont/DefaultSoundfontLoader.js +16 -11
  60. package/dist/soundfont/SoundfontService.d.ts.map +1 -1
  61. package/dist/soundfont/SoundfontService.js +3 -6
  62. package/dist/ui/RegionClipResolver.d.ts.map +1 -1
  63. package/dist/ui/RegionClipResolver.js +15 -6
  64. package/dist/ui/RegionModifyStrategies.js +3 -3
  65. package/dist/ui/TimeGrid.d.ts +2 -1
  66. package/dist/ui/TimeGrid.d.ts.map +1 -1
  67. package/dist/ui/TimeGrid.js +28 -14
  68. package/dist/workers-main.js +1 -1
  69. package/dist/workers-main.js.map +3 -3
  70. package/package.json +16 -16
@@ -1,19 +1,53 @@
1
- import { Errors, isDefined, isNotNull, RuntimeNotifier, Terminator } from "@opendaw/lib-std";
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
- return this.#startListeningControl(field, MidiData.readChannel(data), MidiData.readParam1(data), event);
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 Keys...",
36
- message: "Hit a key on your midi-device to learn a connection.",
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
- #startListeningControl(field, channel, controlId, event) {
49
- console.debug(`startListeningControl channel: ${channel}, controlId: ${controlId}`);
50
- const { observer, terminate } = this.#createMidiControlObserver(this.#project, this.#project.parameterFieldAdapters.get(field.address), controlId);
51
- if (isDefined(event)) {
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) => project.editing.modify(() => adapter.setValue(adapter.valueMapping.y(value)), false);
78
- return {
79
- observer: (event) => {
80
- const data = event.data;
81
- if (data === null) {
82
- return;
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
- if (MidiData.isController(data) && MidiData.readParam1(data) === controlId) {
85
- const value = MidiData.asValue(data);
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
  }