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,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all MIDI-related errors
|
|
3
|
+
*/
|
|
4
|
+
export class MIDIError extends Error {
|
|
5
|
+
constructor(message, code) {
|
|
6
|
+
super(message)
|
|
7
|
+
this.name = "MIDIError"
|
|
8
|
+
this.code = code
|
|
9
|
+
|
|
10
|
+
// Maintain proper stack trace
|
|
11
|
+
if (Error.captureStackTrace) {
|
|
12
|
+
Error.captureStackTrace(this, this.constructor)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Error thrown when MIDI access fails due to browser support or permissions
|
|
19
|
+
*/
|
|
20
|
+
export class MIDIAccessError extends MIDIError {
|
|
21
|
+
constructor(message, reason) {
|
|
22
|
+
super(message, "MIDI_ACCESS_ERROR")
|
|
23
|
+
this.name = "MIDIAccessError"
|
|
24
|
+
this.reason = reason
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Error thrown when MIDI connection fails or is not initialized
|
|
30
|
+
*/
|
|
31
|
+
export class MIDIConnectionError extends MIDIError {
|
|
32
|
+
constructor(message) {
|
|
33
|
+
super(message, "MIDI_CONNECTION_ERROR")
|
|
34
|
+
this.name = "MIDIConnectionError"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Error thrown when a MIDI device cannot be found or accessed
|
|
40
|
+
*/
|
|
41
|
+
export class MIDIDeviceError extends MIDIError {
|
|
42
|
+
constructor(message, deviceType, deviceId) {
|
|
43
|
+
super(message, "MIDI_DEVICE_ERROR")
|
|
44
|
+
this.name = "MIDIDeviceError"
|
|
45
|
+
this.deviceType = deviceType // 'input' or 'output'
|
|
46
|
+
this.deviceId = deviceId
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Error thrown when MIDI validation fails (invalid parameters, formats, etc.)
|
|
52
|
+
*/
|
|
53
|
+
export class MIDIValidationError extends MIDIError {
|
|
54
|
+
constructor(message, validationType) {
|
|
55
|
+
super(message, "MIDI_VALIDATION_ERROR")
|
|
56
|
+
this.name = "MIDIValidationError"
|
|
57
|
+
this.validationType = validationType // 'note', 'patch', 'callback', etc.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Base error class for all DX7-related errors
|
|
63
|
+
*/
|
|
64
|
+
export class DX7Error extends Error {
|
|
65
|
+
constructor(message, code) {
|
|
66
|
+
super(message)
|
|
67
|
+
this.name = "DX7Error"
|
|
68
|
+
this.code = code
|
|
69
|
+
|
|
70
|
+
// Maintain proper stack trace
|
|
71
|
+
if (Error.captureStackTrace) {
|
|
72
|
+
Error.captureStackTrace(this, this.constructor)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Error thrown when DX7 SysEx data parsing fails
|
|
79
|
+
*/
|
|
80
|
+
export class DX7ParseError extends DX7Error {
|
|
81
|
+
constructor(message, parseType, offset) {
|
|
82
|
+
super(message, "DX7_PARSE_ERROR")
|
|
83
|
+
this.name = "DX7ParseError"
|
|
84
|
+
this.parseType = parseType // 'header', 'voice', 'bank', etc.
|
|
85
|
+
this.offset = offset // Byte offset where error occurred
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Error thrown when DX7 parameter validation fails
|
|
91
|
+
*/
|
|
92
|
+
export class DX7ValidationError extends DX7Error {
|
|
93
|
+
constructor(message, validationType, value) {
|
|
94
|
+
super(message, "DX7_VALIDATION_ERROR")
|
|
95
|
+
this.name = "DX7ValidationError"
|
|
96
|
+
this.validationType = validationType // 'offset', 'length', 'parameter', etc.
|
|
97
|
+
this.value = value // Invalid value that caused the error
|
|
98
|
+
}
|
|
99
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* midiwire - Browser-based MIDI controller framework
|
|
3
|
+
*
|
|
4
|
+
* A lightweight, zero-dependency library for creating web-based MIDI controllers.
|
|
5
|
+
* Features declarative HTML binding via data attributes, programmatic APIs,
|
|
6
|
+
* bidirectional MIDI communication, SysEx support, voice management, and more.
|
|
7
|
+
*
|
|
8
|
+
* Works with the Web MIDI API in Chrome, Firefox, and Opera.
|
|
9
|
+
*
|
|
10
|
+
* @module midiwire
|
|
11
|
+
* @see {@link https://github.com/alexferl/midiwire} for documentation
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { DataAttributeBinder } from "./bindings/DataAttributeBinder.js"
|
|
15
|
+
import { MIDIController } from "./core/MIDIController.js"
|
|
16
|
+
import { MIDIDeviceManager } from "./core/MIDIDeviceManager.js"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} MIDIControlsOptions
|
|
20
|
+
* @property {string} [selector="[data-midi-cc]"] - CSS selector for auto-binding
|
|
21
|
+
* @property {number} [channel=1] - Default MIDI channel (1-16)
|
|
22
|
+
* @property {string|number} [output] - MIDI output device name, ID, or index
|
|
23
|
+
* @property {boolean} [sysex=false] - Request SysEx access
|
|
24
|
+
* @property {boolean} [autoConnect=true] - Auto-connect to first available output
|
|
25
|
+
* @property {boolean} [watchDOM=false] - Automatically bind dynamically added elements
|
|
26
|
+
* @property {Function} [onReady] - Callback when MIDI is ready
|
|
27
|
+
* @property {Function} [onError] - Error handler
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create and initialize a MIDI controller
|
|
32
|
+
* @param {MIDIControlsOptions} [options={}]
|
|
33
|
+
* @returns {Promise<MIDIController>}
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* // Auto-bind with data attributes
|
|
37
|
+
* const midi = await createMIDIController({
|
|
38
|
+
* channel: 1,
|
|
39
|
+
* output: "My Synth",
|
|
40
|
+
* selector: "[data-midi-cc]"
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* // Programmatic binding
|
|
45
|
+
* const midi = await createMIDIController({ autoConnect: false });
|
|
46
|
+
* await midi.setOutput("My Synth");
|
|
47
|
+
* midi.bind(document.querySelector("#cutoff"), { cc: 74 });
|
|
48
|
+
*/
|
|
49
|
+
export async function createMIDIController(options = {}) {
|
|
50
|
+
const controller = new MIDIController(options)
|
|
51
|
+
await controller.initialize()
|
|
52
|
+
|
|
53
|
+
const selector = options.selector || "[data-midi-cc]"
|
|
54
|
+
if (selector) {
|
|
55
|
+
const binder = new DataAttributeBinder(controller, selector)
|
|
56
|
+
binder.bindAll()
|
|
57
|
+
|
|
58
|
+
if (options.watchDOM) {
|
|
59
|
+
binder.enableAutoBinding()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Store binder for cleanup
|
|
63
|
+
controller._binder = binder
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return controller
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @typedef {Object} MIDIDeviceManagerOptions
|
|
71
|
+
* @property {Function} [onStatusUpdate] - Callback for status updates (message, state)
|
|
72
|
+
* @property {Function} [onConnectionUpdate] - Callback when connection status changes
|
|
73
|
+
* @property {number} [channel=1] - Default MIDI channel
|
|
74
|
+
* @property {string} [output] - MIDI output device name, ID, or index
|
|
75
|
+
* @property {boolean} [sysex=false] - Request SysEx access
|
|
76
|
+
* @property {Function} [onReady] - Callback when MIDI is ready (midi, deviceManager)
|
|
77
|
+
* @property {Function} [onError] - Error handler
|
|
78
|
+
* @property {string} [selector] - CSS selector for auto-binding controls
|
|
79
|
+
* @property {boolean} [watchDOM=false] - Automatically bind dynamically added elements
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create a MIDIDeviceManager with an integrated MIDIController
|
|
84
|
+
* @param {MIDIDeviceManagerOptions} [options={}]
|
|
85
|
+
* @returns {Promise<MIDIDeviceManager>}
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* // Basic usage
|
|
89
|
+
* const deviceManager = await createMIDIDeviceManager({
|
|
90
|
+
* channel: 1,
|
|
91
|
+
* onStatusUpdate: (message, state) => console.log(message)
|
|
92
|
+
* });
|
|
93
|
+
*
|
|
94
|
+
* // Access the MIDIController via deviceManager.midi
|
|
95
|
+
* const midi = deviceManager.midi;
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* // Auto-connect to a specific device
|
|
99
|
+
* const deviceManager = await createMIDIDeviceManager({
|
|
100
|
+
* output: "My Synth",
|
|
101
|
+
* channel: 2,
|
|
102
|
+
* onStatusUpdate: (message, state) => console.log(message)
|
|
103
|
+
* });
|
|
104
|
+
*/
|
|
105
|
+
export async function createMIDIDeviceManager(options = {}) {
|
|
106
|
+
const {
|
|
107
|
+
onStatusUpdate,
|
|
108
|
+
onConnectionUpdate,
|
|
109
|
+
channel,
|
|
110
|
+
output,
|
|
111
|
+
sysex,
|
|
112
|
+
onReady,
|
|
113
|
+
onError,
|
|
114
|
+
selector,
|
|
115
|
+
watchDOM,
|
|
116
|
+
...otherOptions
|
|
117
|
+
} = options
|
|
118
|
+
const selectorToUse = selector || "[data-midi-cc]"
|
|
119
|
+
|
|
120
|
+
const midi = await createMIDIController({
|
|
121
|
+
autoConnect: false,
|
|
122
|
+
sysex,
|
|
123
|
+
channel: channel || 1,
|
|
124
|
+
selector: selectorToUse,
|
|
125
|
+
watchDOM,
|
|
126
|
+
onError,
|
|
127
|
+
...otherOptions,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
const deviceManager = new MIDIDeviceManager({
|
|
131
|
+
midiController: midi,
|
|
132
|
+
onStatusUpdate: onStatusUpdate || (() => {}),
|
|
133
|
+
onConnectionUpdate: onConnectionUpdate || (() => {}),
|
|
134
|
+
channel: channel || 1,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
if (output) {
|
|
138
|
+
try {
|
|
139
|
+
await midi.setOutput(output)
|
|
140
|
+
deviceManager.currentDevice = midi.getCurrentOutput()
|
|
141
|
+
deviceManager.updateConnectionStatus()
|
|
142
|
+
} catch (err) {
|
|
143
|
+
if (onError) onError(err)
|
|
144
|
+
else console.error("Failed to connect to MIDI device:", err.message)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (onReady) {
|
|
149
|
+
onReady(midi, deviceManager)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return deviceManager
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export { DataAttributeBinder } from "./bindings/DataAttributeBinder.js"
|
|
156
|
+
export { EventEmitter } from "./core/EventEmitter.js"
|
|
157
|
+
export {
|
|
158
|
+
DX7Error,
|
|
159
|
+
DX7ParseError,
|
|
160
|
+
DX7ValidationError,
|
|
161
|
+
MIDIAccessError,
|
|
162
|
+
MIDIConnectionError,
|
|
163
|
+
MIDIDeviceError,
|
|
164
|
+
MIDIError,
|
|
165
|
+
MIDIValidationError,
|
|
166
|
+
} from "./core/errors.js"
|
|
167
|
+
export {
|
|
168
|
+
CONNECTION_EVENTS,
|
|
169
|
+
CONNECTION_EVENTS as CONN,
|
|
170
|
+
MIDIConnection,
|
|
171
|
+
} from "./core/MIDIConnection.js"
|
|
172
|
+
export {
|
|
173
|
+
CONTROLLER_EVENTS,
|
|
174
|
+
CONTROLLER_EVENTS as CTRL,
|
|
175
|
+
MIDIController,
|
|
176
|
+
} from "./core/MIDIController.js"
|
|
177
|
+
export { MIDIDeviceManager } from "./core/MIDIDeviceManager.js"
|
|
178
|
+
export { DX7Bank, DX7Voice } from "./utils/dx7.js"
|
|
179
|
+
export * from "./utils/midi.js"
|
|
180
|
+
export * from "./utils/sysex.js"
|
|
181
|
+
export * from "./utils/validators.js"
|