midiwire 0.1.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/LICENSE +21 -0
- package/README.md +845 -0
- package/dist/midiwire.es.js +1987 -0
- package/dist/midiwire.umd.js +1 -0
- package/package.json +58 -0
- package/src/bindings/DataAttributeBinder.js +198 -0
- package/src/bindings/DataAttributeBinder.test.js +825 -0
- package/src/core/EventEmitter.js +93 -0
- package/src/core/EventEmitter.test.js +357 -0
- package/src/core/MIDIConnection.js +364 -0
- package/src/core/MIDIConnection.test.js +783 -0
- package/src/core/MIDIController.js +756 -0
- package/src/core/MIDIController.test.js +1958 -0
- package/src/core/MIDIDeviceManager.js +204 -0
- package/src/core/MIDIDeviceManager.test.js +638 -0
- package/src/core/errors.js +99 -0
- package/src/index.js +181 -0
- package/src/utils/dx7.js +1294 -0
- package/src/utils/dx7.test.js +1208 -0
- package/src/utils/midi.js +244 -0
- package/src/utils/midi.test.js +260 -0
- package/src/utils/sysex.js +98 -0
- package/src/utils/sysex.test.js +222 -0
- package/src/utils/validators.js +88 -0
- package/src/utils/validators.test.js +300 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight browser-compatible event emitter.
|
|
3
|
+
* Provides publish/subscribe functionality for MIDI events
|
|
4
|
+
* without external dependencies. Implements standard event
|
|
5
|
+
* emitter pattern: register listeners with on(), emit events
|
|
6
|
+
* with emit(), remove listeners with off(). Listener cleanup
|
|
7
|
+
* is automatic via returned unsubscribe functions.
|
|
8
|
+
*/
|
|
9
|
+
export class EventEmitter {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.events = new Map()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Register an event listener
|
|
16
|
+
* @param {string} event - Event name
|
|
17
|
+
* @param {Function} handler - Event handler function
|
|
18
|
+
* @returns {Function} Unsubscribe function
|
|
19
|
+
*/
|
|
20
|
+
on(event, handler) {
|
|
21
|
+
if (!this.events.has(event)) {
|
|
22
|
+
this.events.set(event, [])
|
|
23
|
+
}
|
|
24
|
+
this.events.get(event).push(handler)
|
|
25
|
+
|
|
26
|
+
// Return unsubscribe function
|
|
27
|
+
return () => this.off(event, handler)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Register a one-time event listener
|
|
32
|
+
* @param {string} event - Event name
|
|
33
|
+
* @param {Function} handler - Event handler function
|
|
34
|
+
*/
|
|
35
|
+
once(event, handler) {
|
|
36
|
+
const onceHandler = (...args) => {
|
|
37
|
+
handler(...args)
|
|
38
|
+
this.off(event, onceHandler)
|
|
39
|
+
}
|
|
40
|
+
this.on(event, onceHandler)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Remove an event listener
|
|
45
|
+
* @param {string} event - Event name
|
|
46
|
+
* @param {Function} handler - Event handler function
|
|
47
|
+
*/
|
|
48
|
+
off(event, handler) {
|
|
49
|
+
if (!this.events.has(event)) return
|
|
50
|
+
|
|
51
|
+
const handlers = this.events.get(event)
|
|
52
|
+
const index = handlers.indexOf(handler)
|
|
53
|
+
if (index > -1) {
|
|
54
|
+
handlers.splice(index, 1)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (handlers.length === 0) {
|
|
58
|
+
this.events.delete(event)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Emit an event
|
|
64
|
+
* @param {string} event - Event name
|
|
65
|
+
* @param {*} data - Event data
|
|
66
|
+
*/
|
|
67
|
+
emit(event, data) {
|
|
68
|
+
if (!this.events.has(event)) return
|
|
69
|
+
|
|
70
|
+
// Create a copy to avoid modification during iteration
|
|
71
|
+
const handlers = [...this.events.get(event)]
|
|
72
|
+
|
|
73
|
+
handlers.forEach((handler) => {
|
|
74
|
+
try {
|
|
75
|
+
handler(data)
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error(`Error in event handler for "${event}":`, err)
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Remove all event listeners
|
|
84
|
+
* @param {string} [event] - Optional event name to clear specific event
|
|
85
|
+
*/
|
|
86
|
+
removeAllListeners(event) {
|
|
87
|
+
if (event) {
|
|
88
|
+
this.events.delete(event)
|
|
89
|
+
} else {
|
|
90
|
+
this.events.clear()
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest"
|
|
2
|
+
import { EventEmitter } from "./EventEmitter.js"
|
|
3
|
+
|
|
4
|
+
describe("EventEmitter", () => {
|
|
5
|
+
describe("on and emit", () => {
|
|
6
|
+
it("should register and trigger event handlers", () => {
|
|
7
|
+
const emitter = new EventEmitter()
|
|
8
|
+
const handler = vi.fn()
|
|
9
|
+
|
|
10
|
+
emitter.on("test", handler)
|
|
11
|
+
emitter.emit("test", "data")
|
|
12
|
+
|
|
13
|
+
expect(handler).toHaveBeenCalledTimes(1)
|
|
14
|
+
expect(handler).toHaveBeenCalledWith("data")
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it("should support multiple handlers for same event", () => {
|
|
18
|
+
const emitter = new EventEmitter()
|
|
19
|
+
const handler1 = vi.fn()
|
|
20
|
+
const handler2 = vi.fn()
|
|
21
|
+
|
|
22
|
+
emitter.on("test", handler1)
|
|
23
|
+
emitter.on("test", handler2)
|
|
24
|
+
emitter.emit("test", "data")
|
|
25
|
+
|
|
26
|
+
expect(handler1).toHaveBeenCalledTimes(1)
|
|
27
|
+
expect(handler2).toHaveBeenCalledTimes(1)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it("should pass single data argument to handlers", () => {
|
|
31
|
+
const emitter = new EventEmitter()
|
|
32
|
+
const handler = vi.fn()
|
|
33
|
+
|
|
34
|
+
emitter.on("test", handler)
|
|
35
|
+
emitter.emit("test", "arg1")
|
|
36
|
+
|
|
37
|
+
expect(handler).toHaveBeenCalledWith("arg1")
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it("should not call handlers for different events", () => {
|
|
41
|
+
const emitter = new EventEmitter()
|
|
42
|
+
const handler = vi.fn()
|
|
43
|
+
|
|
44
|
+
emitter.on("test1", handler)
|
|
45
|
+
emitter.emit("test2", "data")
|
|
46
|
+
|
|
47
|
+
expect(handler).not.toHaveBeenCalled()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it("should handle events with no handlers", () => {
|
|
51
|
+
const emitter = new EventEmitter()
|
|
52
|
+
expect(() => emitter.emit("test", "data")).not.toThrow()
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe("once", () => {
|
|
57
|
+
it("should only call handler once", () => {
|
|
58
|
+
const emitter = new EventEmitter()
|
|
59
|
+
const handler = vi.fn()
|
|
60
|
+
|
|
61
|
+
emitter.once("test", handler)
|
|
62
|
+
emitter.emit("test", "data1")
|
|
63
|
+
emitter.emit("test", "data2")
|
|
64
|
+
|
|
65
|
+
expect(handler).toHaveBeenCalledTimes(1)
|
|
66
|
+
expect(handler).toHaveBeenCalledWith("data1")
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it("should handle multiple once handlers", () => {
|
|
70
|
+
const emitter = new EventEmitter()
|
|
71
|
+
const handler1 = vi.fn()
|
|
72
|
+
const handler2 = vi.fn()
|
|
73
|
+
|
|
74
|
+
emitter.once("test", handler1)
|
|
75
|
+
emitter.once("test", handler2)
|
|
76
|
+
emitter.emit("test", "data")
|
|
77
|
+
|
|
78
|
+
// Both handlers should be called once each
|
|
79
|
+
expect(handler1).toHaveBeenCalledTimes(1)
|
|
80
|
+
expect(handler2).toHaveBeenCalledTimes(1)
|
|
81
|
+
expect(handler1).toHaveBeenCalledWith("data")
|
|
82
|
+
expect(handler2).toHaveBeenCalledWith("data")
|
|
83
|
+
|
|
84
|
+
emitter.emit("test", "data2")
|
|
85
|
+
|
|
86
|
+
// Neither should be called again
|
|
87
|
+
expect(handler1).toHaveBeenCalledTimes(1)
|
|
88
|
+
expect(handler2).toHaveBeenCalledTimes(1)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it("should allow mixing once and regular handlers", () => {
|
|
92
|
+
const emitter = new EventEmitter()
|
|
93
|
+
const onceHandler = vi.fn()
|
|
94
|
+
const regularHandler = vi.fn()
|
|
95
|
+
|
|
96
|
+
emitter.once("test", onceHandler)
|
|
97
|
+
emitter.on("test", regularHandler)
|
|
98
|
+
|
|
99
|
+
emitter.emit("test", "data1")
|
|
100
|
+
emitter.emit("test", "data2")
|
|
101
|
+
|
|
102
|
+
// onceHandler should be called once, regularHandler twice
|
|
103
|
+
expect(onceHandler).toHaveBeenCalledTimes(1)
|
|
104
|
+
expect(onceHandler).toHaveBeenCalledWith("data1")
|
|
105
|
+
expect(regularHandler).toHaveBeenCalledTimes(2)
|
|
106
|
+
expect(regularHandler).toHaveBeenNthCalledWith(1, "data1")
|
|
107
|
+
expect(regularHandler).toHaveBeenNthCalledWith(2, "data2")
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it("should handle once handler removing itself during emit", () => {
|
|
111
|
+
const emitter = new EventEmitter()
|
|
112
|
+
const handler1 = vi.fn()
|
|
113
|
+
const handler2 = vi.fn()
|
|
114
|
+
const handler3 = vi.fn()
|
|
115
|
+
|
|
116
|
+
emitter.once("test", handler1)
|
|
117
|
+
emitter.once("test", handler2)
|
|
118
|
+
emitter.once("test", handler3)
|
|
119
|
+
|
|
120
|
+
emitter.emit("test", "data")
|
|
121
|
+
|
|
122
|
+
// All three handlers should be called despite self-removal
|
|
123
|
+
expect(handler1).toHaveBeenCalledTimes(1)
|
|
124
|
+
expect(handler2).toHaveBeenCalledTimes(1)
|
|
125
|
+
expect(handler3).toHaveBeenCalledTimes(1)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
describe("off", () => {
|
|
130
|
+
it("should remove a handler", () => {
|
|
131
|
+
const emitter = new EventEmitter()
|
|
132
|
+
const handler = vi.fn()
|
|
133
|
+
|
|
134
|
+
emitter.on("test", handler)
|
|
135
|
+
emitter.off("test", handler)
|
|
136
|
+
emitter.emit("test", "data")
|
|
137
|
+
|
|
138
|
+
expect(handler).not.toHaveBeenCalled()
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it("should only remove specified handler", () => {
|
|
142
|
+
const emitter = new EventEmitter()
|
|
143
|
+
const handler1 = vi.fn()
|
|
144
|
+
const handler2 = vi.fn()
|
|
145
|
+
|
|
146
|
+
emitter.on("test", handler1)
|
|
147
|
+
emitter.on("test", handler2)
|
|
148
|
+
emitter.off("test", handler1)
|
|
149
|
+
emitter.emit("test", "data")
|
|
150
|
+
|
|
151
|
+
expect(handler1).not.toHaveBeenCalled()
|
|
152
|
+
expect(handler2).toHaveBeenCalledTimes(1)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it("should handle removing non-existent handler", () => {
|
|
156
|
+
const emitter = new EventEmitter()
|
|
157
|
+
const handler = vi.fn()
|
|
158
|
+
|
|
159
|
+
expect(() => emitter.off("test", handler)).not.toThrow()
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it("should handle removing from non-existent event", () => {
|
|
163
|
+
const emitter = new EventEmitter()
|
|
164
|
+
const handler = vi.fn()
|
|
165
|
+
|
|
166
|
+
expect(() => emitter.off("nonexistent", handler)).not.toThrow()
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it("should properly clean up empty event handlers", () => {
|
|
170
|
+
const emitter = new EventEmitter()
|
|
171
|
+
const handler = vi.fn()
|
|
172
|
+
|
|
173
|
+
emitter.on("test", handler)
|
|
174
|
+
emitter.off("test", handler)
|
|
175
|
+
|
|
176
|
+
// Emitting should not throw even though internal array was removed
|
|
177
|
+
expect(() => emitter.emit("test", "data")).not.toThrow()
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
describe("removeAllListeners", () => {
|
|
182
|
+
it("should remove all handlers from a specific event", () => {
|
|
183
|
+
const emitter = new EventEmitter()
|
|
184
|
+
const handler1 = vi.fn()
|
|
185
|
+
const handler2 = vi.fn()
|
|
186
|
+
|
|
187
|
+
emitter.on("test", handler1)
|
|
188
|
+
emitter.on("test", handler2)
|
|
189
|
+
emitter.on("other", handler1)
|
|
190
|
+
|
|
191
|
+
emitter.removeAllListeners("test")
|
|
192
|
+
emitter.emit("test", "data")
|
|
193
|
+
emitter.emit("other", "data")
|
|
194
|
+
|
|
195
|
+
expect(handler1).toHaveBeenCalledTimes(1)
|
|
196
|
+
expect(handler2).not.toHaveBeenCalled()
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it("should remove all handlers from all events", () => {
|
|
200
|
+
const emitter = new EventEmitter()
|
|
201
|
+
const handler1 = vi.fn()
|
|
202
|
+
const handler2 = vi.fn()
|
|
203
|
+
|
|
204
|
+
emitter.on("test", handler1)
|
|
205
|
+
emitter.on("other", handler2)
|
|
206
|
+
|
|
207
|
+
emitter.removeAllListeners()
|
|
208
|
+
emitter.emit("test", "data")
|
|
209
|
+
emitter.emit("other", "data")
|
|
210
|
+
|
|
211
|
+
expect(handler1).not.toHaveBeenCalled()
|
|
212
|
+
expect(handler2).not.toHaveBeenCalled()
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it("should handle removing from non-existent event", () => {
|
|
216
|
+
const emitter = new EventEmitter()
|
|
217
|
+
expect(() => emitter.removeAllListeners("nonexistent")).not.toThrow()
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
describe("on() return value", () => {
|
|
222
|
+
it("should return unsubscribe function", () => {
|
|
223
|
+
const emitter = new EventEmitter()
|
|
224
|
+
const handler = vi.fn()
|
|
225
|
+
|
|
226
|
+
const unsubscribe = emitter.on("test", handler)
|
|
227
|
+
emitter.emit("test", "data")
|
|
228
|
+
expect(handler).toHaveBeenCalledTimes(1)
|
|
229
|
+
|
|
230
|
+
unsubscribe()
|
|
231
|
+
emitter.emit("test", "data")
|
|
232
|
+
expect(handler).toHaveBeenCalledTimes(1)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it("should handle multiple unsubscribe calls", () => {
|
|
236
|
+
const emitter = new EventEmitter()
|
|
237
|
+
const handler = vi.fn()
|
|
238
|
+
|
|
239
|
+
const unsubscribe = emitter.on("test", handler)
|
|
240
|
+
unsubscribe()
|
|
241
|
+
unsubscribe() // Should not throw
|
|
242
|
+
|
|
243
|
+
emitter.emit("test", "data")
|
|
244
|
+
expect(handler).not.toHaveBeenCalled()
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
describe("error handling", () => {
|
|
249
|
+
it("should continue emitting when handler throws", () => {
|
|
250
|
+
const emitter = new EventEmitter()
|
|
251
|
+
const throwingHandler = vi.fn(() => {
|
|
252
|
+
throw new Error("Handler error")
|
|
253
|
+
})
|
|
254
|
+
const normalHandler = vi.fn()
|
|
255
|
+
|
|
256
|
+
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {})
|
|
257
|
+
|
|
258
|
+
emitter.on("test", throwingHandler)
|
|
259
|
+
emitter.on("test", normalHandler)
|
|
260
|
+
|
|
261
|
+
expect(() => emitter.emit("test", "data")).not.toThrow()
|
|
262
|
+
|
|
263
|
+
expect(throwingHandler).toHaveBeenCalled()
|
|
264
|
+
expect(normalHandler).toHaveBeenCalledTimes(1)
|
|
265
|
+
|
|
266
|
+
consoleError.mockRestore()
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
it("should emit all handlers even if one throws", () => {
|
|
270
|
+
const emitter = new EventEmitter()
|
|
271
|
+
const errorHandler = vi.fn(() => {
|
|
272
|
+
throw new Error("Error")
|
|
273
|
+
})
|
|
274
|
+
const handler1 = vi.fn()
|
|
275
|
+
const handler2 = vi.fn()
|
|
276
|
+
|
|
277
|
+
vi.spyOn(console, "error").mockImplementation(() => {})
|
|
278
|
+
|
|
279
|
+
emitter.on("test", handler1)
|
|
280
|
+
emitter.on("test", errorHandler)
|
|
281
|
+
emitter.on("test", handler2)
|
|
282
|
+
|
|
283
|
+
emitter.emit("test", "data")
|
|
284
|
+
|
|
285
|
+
expect(handler1).toHaveBeenCalledTimes(1)
|
|
286
|
+
expect(handler2).toHaveBeenCalledTimes(1)
|
|
287
|
+
|
|
288
|
+
vi.restoreAllMocks()
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
describe("multiple events", () => {
|
|
293
|
+
it("should handle multiple different events independently", () => {
|
|
294
|
+
const emitter = new EventEmitter()
|
|
295
|
+
const handler1 = vi.fn()
|
|
296
|
+
const handler2 = vi.fn()
|
|
297
|
+
const handler3 = vi.fn()
|
|
298
|
+
|
|
299
|
+
emitter.on("event1", handler1)
|
|
300
|
+
emitter.on("event2", handler2)
|
|
301
|
+
emitter.on("event1", handler3)
|
|
302
|
+
|
|
303
|
+
emitter.emit("event1", "data1")
|
|
304
|
+
emitter.emit("event2", "data2")
|
|
305
|
+
|
|
306
|
+
expect(handler1).toHaveBeenCalledTimes(1)
|
|
307
|
+
expect(handler1).toHaveBeenCalledWith("data1")
|
|
308
|
+
expect(handler2).toHaveBeenCalledTimes(1)
|
|
309
|
+
expect(handler2).toHaveBeenCalledWith("data2")
|
|
310
|
+
expect(handler3).toHaveBeenCalledTimes(1)
|
|
311
|
+
expect(handler3).toHaveBeenCalledWith("data1")
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it("should handle large number of events", () => {
|
|
315
|
+
const emitter = new EventEmitter()
|
|
316
|
+
const handlers = []
|
|
317
|
+
|
|
318
|
+
for (let i = 0; i < 100; i++) {
|
|
319
|
+
handlers.push(function handler() {})
|
|
320
|
+
emitter.on(`event${i}`, handlers[i])
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
expect(() => emitter.emit("event50", "test")).not.toThrow()
|
|
324
|
+
})
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
describe("handler order", () => {
|
|
328
|
+
it("should call handlers in order of registration", () => {
|
|
329
|
+
const emitter = new EventEmitter()
|
|
330
|
+
const calls = []
|
|
331
|
+
|
|
332
|
+
emitter.on("test", () => calls.push(1))
|
|
333
|
+
emitter.on("test", () => calls.push(2))
|
|
334
|
+
emitter.on("test", () => calls.push(3))
|
|
335
|
+
|
|
336
|
+
emitter.emit("test")
|
|
337
|
+
|
|
338
|
+
expect(calls).toEqual([1, 2, 3])
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
it("should maintain order when removing middle handler", () => {
|
|
342
|
+
const emitter = new EventEmitter()
|
|
343
|
+
const calls = []
|
|
344
|
+
|
|
345
|
+
const handler2 = () => calls.push(2)
|
|
346
|
+
|
|
347
|
+
emitter.on("test", () => calls.push(1))
|
|
348
|
+
emitter.on("test", handler2)
|
|
349
|
+
emitter.on("test", () => calls.push(3))
|
|
350
|
+
|
|
351
|
+
emitter.off("test", handler2)
|
|
352
|
+
emitter.emit("test")
|
|
353
|
+
|
|
354
|
+
expect(calls).toEqual([1, 3])
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
})
|