@blibliki/engine 0.5.1 → 0.9.0
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/README.md +22 -2
- package/dist/index.d.ts +501 -107
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/Engine.ts +46 -29
- package/src/core/index.ts +11 -2
- package/src/core/midi/BaseMidiDevice.ts +47 -0
- package/src/core/midi/ComputerKeyboardDevice.ts +2 -1
- package/src/core/midi/MidiDeviceManager.ts +125 -31
- package/src/core/midi/{MidiDevice.ts → MidiInputDevice.ts} +6 -30
- package/src/core/midi/MidiOutputDevice.ts +23 -0
- package/src/core/midi/adapters/NodeMidiAdapter.ts +99 -13
- package/src/core/midi/adapters/WebMidiAdapter.ts +68 -10
- package/src/core/midi/adapters/types.ts +13 -4
- package/src/core/midi/controllers/BaseController.ts +14 -0
- package/src/core/module/Module.ts +121 -13
- package/src/core/module/PolyModule.ts +36 -0
- package/src/core/module/VoiceScheduler.ts +150 -10
- package/src/core/module/index.ts +9 -4
- package/src/index.ts +27 -3
- package/src/modules/Chorus.ts +222 -0
- package/src/modules/Constant.ts +2 -2
- package/src/modules/Delay.ts +347 -0
- package/src/modules/Distortion.ts +182 -0
- package/src/modules/Envelope.ts +158 -92
- package/src/modules/Filter.ts +7 -7
- package/src/modules/Gain.ts +2 -2
- package/src/modules/LFO.ts +287 -0
- package/src/modules/LegacyEnvelope.ts +146 -0
- package/src/modules/{MidiSelector.ts → MidiInput.ts} +26 -19
- package/src/modules/MidiMapper.ts +59 -4
- package/src/modules/MidiOutput.ts +121 -0
- package/src/modules/Noise.ts +259 -0
- package/src/modules/Oscillator.ts +9 -3
- package/src/modules/Reverb.ts +379 -0
- package/src/modules/Scale.ts +49 -4
- package/src/modules/StepSequencer.ts +410 -22
- package/src/modules/StereoPanner.ts +1 -1
- package/src/modules/index.ts +142 -29
- package/src/processors/custom-envelope-processor.ts +125 -0
- package/src/processors/index.ts +10 -0
- package/src/processors/lfo-processor.ts +123 -0
- package/src/processors/scale-processor.ts +42 -5
- package/src/utils/WetDryMixer.ts +123 -0
- package/src/utils/expandPatternSequence.ts +18 -0
- package/src/utils/index.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blibliki/engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -15,15 +15,15 @@
|
|
|
15
15
|
"dist"
|
|
16
16
|
],
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@types/audioworklet": "0.0.
|
|
19
|
-
"vite-tsconfig-paths": "
|
|
20
|
-
"vitest": "4.0.
|
|
18
|
+
"@types/audioworklet": "0.0.93",
|
|
19
|
+
"vite-tsconfig-paths": "6.0.5",
|
|
20
|
+
"vitest": "4.0.18"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@julusian/midi": "^3.6.1",
|
|
24
|
-
"es-toolkit": "
|
|
25
|
-
"@blibliki/transport": "^0.
|
|
26
|
-
"@blibliki/utils": "^0.
|
|
24
|
+
"es-toolkit": "1.44.0",
|
|
25
|
+
"@blibliki/transport": "^0.9.0",
|
|
26
|
+
"@blibliki/utils": "^0.9.0"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsup",
|
package/src/Engine.ts
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BPM,
|
|
3
|
-
ContextTime,
|
|
4
|
-
Ticks,
|
|
5
|
-
TimeSignature,
|
|
6
|
-
Transport,
|
|
7
|
-
TransportEvent,
|
|
8
|
-
} from "@blibliki/transport";
|
|
1
|
+
import { BPM, Ticks, TimeSignature, Transport } from "@blibliki/transport";
|
|
9
2
|
import {
|
|
10
3
|
assertDefined,
|
|
11
4
|
Context,
|
|
@@ -26,6 +19,7 @@ import {
|
|
|
26
19
|
ModuleParams,
|
|
27
20
|
ModuleType,
|
|
28
21
|
ModuleTypeToModuleMapping,
|
|
22
|
+
ModuleTypeToStateMapping,
|
|
29
23
|
createModule,
|
|
30
24
|
} from "@/modules";
|
|
31
25
|
import {
|
|
@@ -63,7 +57,7 @@ export class Engine {
|
|
|
63
57
|
context: Context;
|
|
64
58
|
isInitialized = false;
|
|
65
59
|
routes: Routes;
|
|
66
|
-
transport: Transport
|
|
60
|
+
transport: Transport;
|
|
67
61
|
modules: Map<
|
|
68
62
|
string,
|
|
69
63
|
ModuleTypeToModuleMapping[keyof ModuleTypeToModuleMapping]
|
|
@@ -107,20 +101,8 @@ export class Engine {
|
|
|
107
101
|
|
|
108
102
|
this.context = context;
|
|
109
103
|
this.transport = new Transport(this.context, {
|
|
110
|
-
generator: (_start: Ticks, _end: Ticks) => {
|
|
111
|
-
return [] as TransportEvent[];
|
|
112
|
-
},
|
|
113
|
-
consumer: (_event: TransportEvent) => {
|
|
114
|
-
return;
|
|
115
|
-
},
|
|
116
|
-
onJump: (_ticks: Ticks) => {
|
|
117
|
-
return;
|
|
118
|
-
},
|
|
119
104
|
onStart: this.onStart,
|
|
120
105
|
onStop: this.onStop,
|
|
121
|
-
silence: (_actionAt: ContextTime) => {
|
|
122
|
-
return;
|
|
123
|
-
},
|
|
124
106
|
});
|
|
125
107
|
this.routes = new Routes(this);
|
|
126
108
|
this.modules = new Map();
|
|
@@ -197,16 +179,19 @@ export class Engine {
|
|
|
197
179
|
|
|
198
180
|
async start() {
|
|
199
181
|
await this.resume();
|
|
200
|
-
this.
|
|
182
|
+
const actionAt = this.context.currentTime;
|
|
183
|
+
this.transport.start(actionAt);
|
|
201
184
|
}
|
|
202
185
|
|
|
203
186
|
stop() {
|
|
204
|
-
this.
|
|
205
|
-
this.transport.
|
|
187
|
+
const actionAt = this.context.currentTime;
|
|
188
|
+
this.transport.stop(actionAt);
|
|
189
|
+
this.transport.reset(actionAt);
|
|
206
190
|
}
|
|
207
191
|
|
|
208
192
|
pause() {
|
|
209
|
-
this.
|
|
193
|
+
const actionAt = this.context.currentTime;
|
|
194
|
+
this.transport.stop(actionAt);
|
|
210
195
|
}
|
|
211
196
|
|
|
212
197
|
get bpm() {
|
|
@@ -273,16 +258,44 @@ export class Engine {
|
|
|
273
258
|
return this.midiDeviceManager.findByFuzzyName(name, threshold);
|
|
274
259
|
}
|
|
275
260
|
|
|
261
|
+
findMidiInputDevice(id: string) {
|
|
262
|
+
return this.midiDeviceManager.findInput(id);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
findMidiInputDeviceByName(name: string) {
|
|
266
|
+
return this.midiDeviceManager.findInputByName(name);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
findMidiInputDeviceByFuzzyName(name: string, threshold?: number) {
|
|
270
|
+
return this.midiDeviceManager.findInputByFuzzyName(name, threshold);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
findMidiOutputDevice(id: string) {
|
|
274
|
+
return this.midiDeviceManager.findOutput(id);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
findMidiOutputDeviceByName(name: string) {
|
|
278
|
+
return this.midiDeviceManager.findOutputByName(name);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
findMidiOutputDeviceByFuzzyName(name: string, threshold?: number) {
|
|
282
|
+
return this.midiDeviceManager.findOutputByFuzzyName(name, threshold);
|
|
283
|
+
}
|
|
284
|
+
|
|
276
285
|
onPropsUpdate(
|
|
277
286
|
callback: <T extends ModuleType>(
|
|
278
|
-
params: IModule<T> | IPolyModule<T
|
|
287
|
+
params: (IModule<T> | IPolyModule<T>) & {
|
|
288
|
+
state?: ModuleTypeToStateMapping[T];
|
|
289
|
+
},
|
|
279
290
|
) => void,
|
|
280
291
|
) {
|
|
281
292
|
this.propsUpdateCallbacks.push(callback);
|
|
282
293
|
}
|
|
283
294
|
|
|
284
295
|
_triggerPropsUpdate<T extends ModuleType>(
|
|
285
|
-
params: IModule<T> | IPolyModule<T
|
|
296
|
+
params: (IModule<T> | IPolyModule<T>) & {
|
|
297
|
+
state?: ModuleTypeToStateMapping[T];
|
|
298
|
+
},
|
|
286
299
|
) {
|
|
287
300
|
this.propsUpdateCallbacks.forEach((callback) => {
|
|
288
301
|
callback(params);
|
|
@@ -301,14 +314,18 @@ export class Engine {
|
|
|
301
314
|
}
|
|
302
315
|
|
|
303
316
|
// actionAt is context time
|
|
304
|
-
private onStart = (
|
|
317
|
+
private onStart = (ticks: Ticks) => {
|
|
318
|
+
const actionAt = this.transport.getContextTimeAtTicks(ticks);
|
|
319
|
+
|
|
305
320
|
this.modules.forEach((module) => {
|
|
306
321
|
module.start(actionAt);
|
|
307
322
|
});
|
|
308
323
|
};
|
|
309
324
|
|
|
310
325
|
// actionAt is context time
|
|
311
|
-
private onStop = (
|
|
326
|
+
private onStop = (ticks: Ticks) => {
|
|
327
|
+
const actionAt = this.transport.getContextTimeAtTicks(ticks);
|
|
328
|
+
|
|
312
329
|
this.modules.forEach((module) => {
|
|
313
330
|
module.stop(actionAt);
|
|
314
331
|
});
|
package/src/core/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type {
|
|
|
5
5
|
IPolyModuleSerialize,
|
|
6
6
|
IAnyModuleSerialize,
|
|
7
7
|
SetterHooks,
|
|
8
|
+
StateSetterHooks,
|
|
8
9
|
} from "./module";
|
|
9
10
|
|
|
10
11
|
export type IAnyAudioContext = AudioContext | OfflineAudioContext;
|
|
@@ -13,8 +14,16 @@ export { Routes } from "./Route";
|
|
|
13
14
|
export type { IRoute } from "./Route";
|
|
14
15
|
|
|
15
16
|
export { default as MidiDeviceManager } from "./midi/MidiDeviceManager";
|
|
16
|
-
export {
|
|
17
|
-
|
|
17
|
+
export {
|
|
18
|
+
default as BaseMidiDevice,
|
|
19
|
+
MidiPortState,
|
|
20
|
+
} from "./midi/BaseMidiDevice";
|
|
21
|
+
export type { IMidiDevice } from "./midi/BaseMidiDevice";
|
|
22
|
+
export { default as MidiInputDevice } from "./midi/MidiInputDevice";
|
|
23
|
+
export type { IMidiInput, EventListerCallback } from "./midi/MidiInputDevice";
|
|
24
|
+
export { default as MidiOutputDevice } from "./midi/MidiOutputDevice";
|
|
25
|
+
// Legacy export for backwards compatibility
|
|
26
|
+
export { default as MidiDevice } from "./midi/MidiInputDevice";
|
|
18
27
|
export { default as MidiEvent, MidiEventType } from "./midi/MidiEvent";
|
|
19
28
|
export {
|
|
20
29
|
normalizeDeviceName,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { IMidiPort } from "./adapters";
|
|
2
|
+
|
|
3
|
+
export enum MidiPortState {
|
|
4
|
+
connected = "connected",
|
|
5
|
+
disconnected = "disconnected",
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type IMidiDevice = {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
state: MidiPortState;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default abstract class BaseMidiDevice<
|
|
15
|
+
T extends IMidiPort,
|
|
16
|
+
> implements IMidiDevice {
|
|
17
|
+
protected midiPort: T;
|
|
18
|
+
|
|
19
|
+
constructor(props: T) {
|
|
20
|
+
this.midiPort = props;
|
|
21
|
+
this.connect();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
abstract connect(): void;
|
|
25
|
+
|
|
26
|
+
abstract disconnect(): void;
|
|
27
|
+
|
|
28
|
+
get id() {
|
|
29
|
+
return this.midiPort.id;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get name() {
|
|
33
|
+
return this.midiPort.name;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get type() {
|
|
37
|
+
return this.midiPort.type;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get state() {
|
|
41
|
+
return this.midiPort.state as MidiPortState;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
serialize() {
|
|
45
|
+
return { id: this.id, name: this.name, type: this.type, state: this.state };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Context } from "@blibliki/utils";
|
|
2
2
|
import Note from "../Note";
|
|
3
|
-
import {
|
|
3
|
+
import { MidiPortState } from "./BaseMidiDevice";
|
|
4
4
|
import MidiEvent from "./MidiEvent";
|
|
5
|
+
import { EventListerCallback, IMidiInput } from "./MidiInputDevice";
|
|
5
6
|
|
|
6
7
|
const MAP_KEYS: Record<string, Note> = {
|
|
7
8
|
a: new Note("C3"),
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { Context } from "@blibliki/utils";
|
|
2
2
|
import ComputerKeyboardDevice from "./ComputerKeyboardDevice";
|
|
3
|
-
import
|
|
3
|
+
import MidiInputDevice from "./MidiInputDevice";
|
|
4
|
+
import MidiOutputDevice from "./MidiOutputDevice";
|
|
4
5
|
import { createMidiAdapter, type IMidiAccess } from "./adapters";
|
|
5
6
|
import { findBestMatch } from "./deviceMatcher";
|
|
6
7
|
|
|
7
|
-
type ListenerCallback = (device:
|
|
8
|
+
type ListenerCallback = (device: MidiInputDevice | MidiOutputDevice) => void;
|
|
8
9
|
|
|
9
10
|
export default class MidiDeviceManager {
|
|
10
|
-
|
|
11
|
+
inputDevices = new Map<string, MidiInputDevice | ComputerKeyboardDevice>();
|
|
12
|
+
outputDevices = new Map<string, MidiOutputDevice>();
|
|
11
13
|
private initialized = false;
|
|
12
14
|
private listeners: ListenerCallback[] = [];
|
|
13
15
|
private context: Readonly<Context>;
|
|
@@ -26,12 +28,47 @@ export default class MidiDeviceManager {
|
|
|
26
28
|
this.initialized = true;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
find(
|
|
30
|
-
|
|
31
|
+
find(
|
|
32
|
+
id: string,
|
|
33
|
+
): MidiInputDevice | ComputerKeyboardDevice | MidiOutputDevice | undefined {
|
|
34
|
+
return this.findInput(id) ?? this.findOutput(id);
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
findByName(
|
|
34
|
-
|
|
37
|
+
findByName(
|
|
38
|
+
name: string,
|
|
39
|
+
): MidiInputDevice | ComputerKeyboardDevice | MidiOutputDevice | undefined {
|
|
40
|
+
return this.findInputByName(name) ?? this.findOutputByName(name);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
findByFuzzyName(
|
|
44
|
+
name: string,
|
|
45
|
+
threshold = 0.6,
|
|
46
|
+
): MidiInputDevice | ComputerKeyboardDevice | MidiOutputDevice | undefined {
|
|
47
|
+
const input = this.findInputByFuzzyName(name, threshold);
|
|
48
|
+
const output = this.findOutputByFuzzyName(name, threshold);
|
|
49
|
+
|
|
50
|
+
if (!input) return output?.device;
|
|
51
|
+
if (!output) return input.device;
|
|
52
|
+
|
|
53
|
+
return input.score > output.score ? input.device : output.device;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
findInput(id: string): MidiInputDevice | ComputerKeyboardDevice | undefined {
|
|
57
|
+
return this.inputDevices.get(id);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
findInputByName(
|
|
61
|
+
name: string,
|
|
62
|
+
): MidiInputDevice | ComputerKeyboardDevice | undefined {
|
|
63
|
+
return Array.from(this.inputDevices.values()).find((d) => d.name === name);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
findOutput(id: string): MidiOutputDevice | undefined {
|
|
67
|
+
return this.outputDevices.get(id);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
findOutputByName(name: string): MidiOutputDevice | undefined {
|
|
71
|
+
return Array.from(this.outputDevices.values()).find((d) => d.name === name);
|
|
35
72
|
}
|
|
36
73
|
|
|
37
74
|
/**
|
|
@@ -42,11 +79,30 @@ export default class MidiDeviceManager {
|
|
|
42
79
|
* @param threshold - Minimum similarity score (0-1, default: 0.6)
|
|
43
80
|
* @returns The best matching device and confidence score, or null
|
|
44
81
|
*/
|
|
45
|
-
|
|
82
|
+
findInputByFuzzyName(
|
|
46
83
|
targetName: string,
|
|
47
84
|
threshold = 0.6,
|
|
48
|
-
): {
|
|
49
|
-
|
|
85
|
+
): {
|
|
86
|
+
device: MidiInputDevice | ComputerKeyboardDevice;
|
|
87
|
+
score: number;
|
|
88
|
+
} | null {
|
|
89
|
+
const deviceEntries = Array.from(this.inputDevices.values());
|
|
90
|
+
const candidateNames = deviceEntries.map((d) => d.name);
|
|
91
|
+
|
|
92
|
+
const match = findBestMatch(targetName, candidateNames, threshold);
|
|
93
|
+
|
|
94
|
+
if (!match) return null;
|
|
95
|
+
|
|
96
|
+
const device = deviceEntries.find((d) => d.name === match.name);
|
|
97
|
+
|
|
98
|
+
return device ? { device, score: match.score } : null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
findOutputByFuzzyName(
|
|
102
|
+
targetName: string,
|
|
103
|
+
threshold = 0.6,
|
|
104
|
+
): { device: MidiOutputDevice; score: number } | null {
|
|
105
|
+
const deviceEntries = Array.from(this.outputDevices.values());
|
|
50
106
|
const candidateNames = deviceEntries.map((d) => d.name);
|
|
51
107
|
|
|
52
108
|
const match = findBestMatch(targetName, candidateNames, threshold);
|
|
@@ -79,8 +135,17 @@ export default class MidiDeviceManager {
|
|
|
79
135
|
}
|
|
80
136
|
|
|
81
137
|
for (const input of this.midiAccess.inputs()) {
|
|
82
|
-
if (!this.
|
|
83
|
-
this.
|
|
138
|
+
if (!this.inputDevices.has(input.id)) {
|
|
139
|
+
this.inputDevices.set(
|
|
140
|
+
input.id,
|
|
141
|
+
new MidiInputDevice(input, this.context),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const output of this.midiAccess.outputs()) {
|
|
147
|
+
if (!this.outputDevices.has(output.id)) {
|
|
148
|
+
this.outputDevices.set(output.id, new MidiOutputDevice(output));
|
|
84
149
|
}
|
|
85
150
|
}
|
|
86
151
|
} catch (err) {
|
|
@@ -92,7 +157,7 @@ export default class MidiDeviceManager {
|
|
|
92
157
|
if (typeof document === "undefined") return;
|
|
93
158
|
|
|
94
159
|
const computerKeyboardDevice = new ComputerKeyboardDevice(this.context);
|
|
95
|
-
this.
|
|
160
|
+
this.inputDevices.set(computerKeyboardDevice.id, computerKeyboardDevice);
|
|
96
161
|
}
|
|
97
162
|
|
|
98
163
|
private listenChanges() {
|
|
@@ -101,26 +166,55 @@ export default class MidiDeviceManager {
|
|
|
101
166
|
this.midiAccess.addEventListener("statechange", (port) => {
|
|
102
167
|
if (port.state === "connected") {
|
|
103
168
|
// Device connected
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
169
|
+
if (port.type === "input") {
|
|
170
|
+
if (this.inputDevices.has(port.id)) return;
|
|
171
|
+
|
|
172
|
+
// Find the actual input port from midiAccess
|
|
173
|
+
for (const input of this.midiAccess!.inputs()) {
|
|
174
|
+
if (input.id === port.id) {
|
|
175
|
+
const device = new MidiInputDevice(input, this.context);
|
|
176
|
+
this.inputDevices.set(device.id, device);
|
|
177
|
+
|
|
178
|
+
this.listeners.forEach((listener) => {
|
|
179
|
+
listener(device);
|
|
180
|
+
});
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
// Output device connected
|
|
186
|
+
if (this.outputDevices.has(port.id)) return;
|
|
187
|
+
|
|
188
|
+
// Find the actual output port from midiAccess
|
|
189
|
+
for (const output of this.midiAccess!.outputs()) {
|
|
190
|
+
if (output.id === port.id) {
|
|
191
|
+
const device = new MidiOutputDevice(output);
|
|
192
|
+
this.outputDevices.set(device.id, device);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
112
197
|
} else {
|
|
113
198
|
// Device disconnected
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
listener
|
|
123
|
-
|
|
199
|
+
if (port.type === "input") {
|
|
200
|
+
const device = this.inputDevices.get(port.id);
|
|
201
|
+
if (!device) return;
|
|
202
|
+
if (device instanceof ComputerKeyboardDevice) return;
|
|
203
|
+
|
|
204
|
+
device.disconnect();
|
|
205
|
+
this.inputDevices.delete(device.id);
|
|
206
|
+
|
|
207
|
+
this.listeners.forEach((listener) => {
|
|
208
|
+
listener(device);
|
|
209
|
+
});
|
|
210
|
+
} else {
|
|
211
|
+
// Output device disconnected
|
|
212
|
+
const device = this.outputDevices.get(port.id);
|
|
213
|
+
if (!device) return;
|
|
214
|
+
|
|
215
|
+
device.disconnect();
|
|
216
|
+
this.outputDevices.delete(device.id);
|
|
217
|
+
}
|
|
124
218
|
}
|
|
125
219
|
});
|
|
126
220
|
}
|
|
@@ -1,67 +1,43 @@
|
|
|
1
1
|
import { Context } from "@blibliki/utils";
|
|
2
|
+
import BaseMidiDevice, { MidiPortState } from "./BaseMidiDevice";
|
|
2
3
|
import Message from "./Message";
|
|
3
4
|
import MidiEvent, { MidiEventType } from "./MidiEvent";
|
|
4
5
|
import type { IMidiInputPort, IMidiMessageEvent } from "./adapters";
|
|
5
6
|
|
|
6
|
-
export
|
|
7
|
-
connected = "connected",
|
|
8
|
-
disconnected = "disconnected",
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type IMidiDevice = {
|
|
7
|
+
export type IMidiInput = {
|
|
12
8
|
id: string;
|
|
13
9
|
name: string;
|
|
14
10
|
state: MidiPortState;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export type IMidiInput = IMidiDevice & {
|
|
18
11
|
eventListerCallbacks: EventListerCallback[];
|
|
19
12
|
};
|
|
20
13
|
|
|
21
14
|
export type EventListerCallback = (event: MidiEvent) => void;
|
|
22
15
|
|
|
23
|
-
export default class
|
|
24
|
-
id: string;
|
|
25
|
-
name: string;
|
|
16
|
+
export default class MidiInputDevice extends BaseMidiDevice<IMidiInputPort> {
|
|
26
17
|
eventListerCallbacks: EventListerCallback[] = [];
|
|
27
18
|
|
|
28
19
|
private context: Readonly<Context>;
|
|
29
|
-
private input: IMidiInputPort;
|
|
30
20
|
private messageHandler: ((event: IMidiMessageEvent) => void) | null = null;
|
|
31
21
|
|
|
32
22
|
constructor(input: IMidiInputPort, context: Context) {
|
|
33
|
-
|
|
34
|
-
this.name = input.name;
|
|
35
|
-
this.input = input;
|
|
23
|
+
super(input);
|
|
36
24
|
this.context = context;
|
|
37
|
-
|
|
38
|
-
this.connect();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
get state() {
|
|
42
|
-
return this.input.state as MidiPortState;
|
|
43
25
|
}
|
|
44
26
|
|
|
45
27
|
connect() {
|
|
46
28
|
this.messageHandler = (e: IMidiMessageEvent) => {
|
|
47
29
|
this.processEvent(e);
|
|
48
30
|
};
|
|
49
|
-
this.
|
|
31
|
+
this.midiPort.addEventListener(this.messageHandler);
|
|
50
32
|
}
|
|
51
33
|
|
|
52
34
|
disconnect() {
|
|
53
35
|
if (this.messageHandler) {
|
|
54
|
-
this.
|
|
36
|
+
this.midiPort.removeEventListener(this.messageHandler);
|
|
55
37
|
this.messageHandler = null;
|
|
56
38
|
}
|
|
57
39
|
}
|
|
58
40
|
|
|
59
|
-
serialize() {
|
|
60
|
-
const { id, name, state } = this;
|
|
61
|
-
|
|
62
|
-
return { id, name, state };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
41
|
addEventListener(callback: EventListerCallback) {
|
|
66
42
|
this.eventListerCallbacks.push(callback);
|
|
67
43
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import BaseMidiDevice from "./BaseMidiDevice";
|
|
2
|
+
import type { IMidiOutputPort } from "./adapters";
|
|
3
|
+
|
|
4
|
+
export default class MidiOutputDevice extends BaseMidiDevice<IMidiOutputPort> {
|
|
5
|
+
constructor(output: IMidiOutputPort) {
|
|
6
|
+
super(output);
|
|
7
|
+
this.connect();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
connect() {
|
|
11
|
+
// Output ports don't require connection setup like inputs
|
|
12
|
+
// This method exists to satisfy the BaseMidiDevice interface
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
disconnect() {
|
|
16
|
+
// Output ports don't require disconnection cleanup like inputs
|
|
17
|
+
// This method exists to satisfy the BaseMidiDevice interface
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
send(data: number[] | Uint8Array, timestamp?: number) {
|
|
21
|
+
this.midiPort.send(data, timestamp);
|
|
22
|
+
}
|
|
23
|
+
}
|