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.
@@ -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"