midiwire - Browser-based MIDI controller framework
A lightweight, zero-dependency library for creating web-based MIDI controllers.
Features declarative HTML binding via data attributes, programmatic APIs,
bidirectional MIDI communication, SysEx support, voice management, and more.
Works with the Web MIDI API in Chrome, Firefox, and Opera. Provides multiple
integration approaches from fully declarative (HTML-only) to fully programmatic (JavaScript-only).
## Features
- Declarative binding via data attributes (zero JavaScript)
- Programmatic binding with full control
- Bidirectional MIDI communication (send/receive)
- SysEx support
- Patch management (save/load presets)
- Hotplug device detection
- Comprehensive event system
- DX7 voice/parameter management
- Source:
- See:
-
- https://github.com/alexferl/midiwire for full documentation
- https://github.com/alexferl/midiwire#quick-start for quick start guide
Examples
// Device manager with UI integration
import { createMIDIDeviceManager } from "midiwire";
const deviceManager = await createMIDIDeviceManager({
onStatusUpdate: (message, state) => {
document.getElementById("status").textContent = message;
}
});
// Setup device dropdown
const select = document.getElementById("device-select");
const channelSelect = document.getElementById("channel-select");
await deviceManager.setupSelectors({
output: select,
channel: channelSelect
});
// Quick start - Auto-binding with data attributes
import { createMIDIController } from "midiwire";
const midi = await createMIDIController({
channel: 1,
selector: "[data-midi-cc]",
watchDOM: true
});
// In your HTML:
// <input type="range" min="0" max="127" data-midi-cc="74" data-midi-label="Filter">
// <input type="range" min="0" max="127" data-midi-cc="7" data-midi-label="Volume">
// Programmatic binding
import { MIDIController } from "midiwire";
const midi = new MIDIController({ channel: 1 });
await midi.init();
await midi.connect("My Synth");
const cutoff = document.getElementById("cutoff");
midi.bind(cutoff, { cc: 74 });
Methods
(static) createMIDIController(optionsopt) → {Promise.<MIDIController>}
Create and initialize a MIDI controller with optional auto-binding. This factory function
creates a MIDIController instance, initializes it, and optionally sets up declarative binding
using DataAttributeBinder. Perfect for getting started quickly or when using data attributes.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
options |
MIDIControlsOptions |
<optional> |
{} | Configuration options |
Throws:
-
If MIDI access is denied or browser doesn't support Web MIDI API
- Type
- MIDIAccessError
Returns:
A promise resolving to the initialized MIDIController instance
- Type
- Promise.<MIDIController>
Examples
// Quick start with auto-binding
const midi = await createMIDIController({
selector: "[data-midi-cc]",
watchDOM: true
});
// Auto-bind with specific device and output channel
const midi = await createMIDIController({
output: "My Synth",
outputChannel: 2,
selector: "[data-midi-cc]",
onReady: (controller) => {
console.log("MIDI ready!");
}
});
// Auto-bind with separate input/output channels
const midi = await createMIDIController({
inputChannel: 1,
outputChannel: 2,
selector: "[data-midi-cc]",
watchDOM: true
});
// Programmatic binding (no auto-bind)
const midi = await createMIDIController({
autoConnect: false,
outputChannel: 1
});
await midi.device.connectOutput("My Synth");
const slider = document.getElementById("cutoff");
midi.bind(slider, { cc: 74 });
// With SysEx support
const midi = await createMIDIController({
sysex: true,
onReady: (controller) => {
// Send SysEx after connection
controller.sendSysEx([0x41, 0x10, 0x42]);
}
});
(static) createMIDIDeviceManager(optionsopt) → {Promise.<MIDIDeviceManager>}
Create a MIDIDeviceManager with an integrated MIDIController. This factory function creates a complete
device management solution including MIDIController, auto-binding (optional), and device manager UI helpers.
Perfect for applications that need device selection UI and status management.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
options |
MIDIDeviceManagerOptions |
<optional> |
{} | Configuration options |
Throws:
-
If MIDI access is denied or browser doesn't support Web MIDI API
- Type
- MIDIAccessError
Returns:
A promise resolving to the initialized MIDIDeviceManager instance
- Type
- Promise.<MIDIDeviceManager>
Examples
// Basic device manager
const deviceManager = await createMIDIDeviceManager({
outputChannel: 1,
onStatusUpdate: (message, state) => {
console.log(message);
}
});
// Access the MIDIController via deviceManager.midi
const midi = deviceManager.midi;
midi.bind(document.getElementById("cutoff"), { cc: 74 });
// With auto-connect and status UI
const deviceManager = await createMIDIDeviceManager({
output: "My Synth",
outputChannel: 2,
selector: "[data-midi-cc]",
onStatusUpdate: (message, state) => {
const statusEl = document.getElementById("status");
statusEl.textContent = message;
statusEl.className = state;
}
});
// Complete UI integration with device dropdown
const deviceManager = await createMIDIDeviceManager({
inputChannel: 1,
outputChannel: 2,
sysex: true,
onStatusUpdate: (message, state) => {
document.getElementById("status").textContent = message;
},
onReady: async (midi, dm) => {
// Setup device and channel selection
const deviceSelect = document.getElementById("device-select");
const channelSelect = document.getElementById("channel-select");
await dm.setupSelectors({
output: deviceSelect,
channel: channelSelect
});
}
});
// With separate input/output devices and channels
const deviceManager = await createMIDIDeviceManager({
inputChannel: 1,
outputChannel: 2,
input: "My MIDI Keyboard",
output: "My Synth Module",
onStatusUpdate: (message, state) => {
document.getElementById("status").textContent = message;
},
onReady: async (midi, dm) => {
// Setup input and output device selectors
const inputSelect = document.getElementById("input-select");
const outputSelect = document.getElementById("output-select");
const channelSelect = document.getElementById("channel-select");
await dm.setupSelectors({
input: inputSelect,
output: outputSelect,
channel: channelSelect
});
}
});
Type Definitions
MIDIControlsOptions
Options for createMIDIController()
Type:
- Object
Properties:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
selector |
string |
<optional> |
"[data-midi-cc]" | CSS selector for auto-binding elements that have data-midi-* attributes |
inputChannel |
number |
<optional> |
1 | Input MIDI channel (1-16) |
outputChannel |
number |
<optional> |
1 | Output MIDI channel (1-16) |
output |
string | number |
<optional> |
MIDI output device name, ID, or index to auto-connect to | |
sysex |
boolean |
<optional> |
false | Request SysEx access for sending/receiving system exclusive messages |
autoConnect |
boolean |
<optional> |
true | Auto-connect to first available output device |
watchDOM |
boolean |
<optional> |
false | Automatically bind dynamically added elements using MutationObserver |
onReady |
function |
<optional> |
Callback when MIDI is ready, receives (controller) as parameter | |
onError |
function |
<optional> |
Error handler for MIDI access errors | |
input |
string | number |
<optional> |
MIDI input device name, ID, or index to connect to for receiving MIDI |
Examples
// Basic auto-binding
const options = {
selector: "[data-midi-cc]",
outputChannel: 1,
watchDOM: true
};
// Connect to specific device
const options = {
output: "My MIDI Keyboard",
outputChannel: 2,
sysex: true
};
// With separate input/output channels
const options = {
inputChannel: 1,
outputChannel: 2,
input: "My MIDI Controller",
output: "My Synth"
};
MIDIDeviceManagerOptions
Options for createMIDIDeviceManager()
Type:
- Object
Properties:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
onStatusUpdate |
function |
<optional> |
Callback for status updates (message: string, state: string) | |
onConnectionUpdate |
function |
<optional> |
Callback when connection status changes (device: Object, midi: MIDIController) | |
inputChannel |
number |
<optional> |
1 | Input MIDI channel (1-16) |
outputChannel |
number |
<optional> |
1 | Output MIDI channel (1-16) |
output |
string | number |
<optional> |
MIDI output device name, ID, or index to auto-connect to | |
sysex |
boolean |
<optional> |
false | Request SysEx access |
onReady |
function |
<optional> |
Callback when MIDI is ready, receives (midi: MIDIController, deviceManager: MIDIDeviceManager) | |
onError |
function |
<optional> |
Error handler for MIDI access errors | |
selector |
string |
<optional> |
"[data-midi-cc]" | CSS selector for auto-binding controls |
watchDOM |
boolean |
<optional> |
false | Automatically bind dynamically added elements |
input |
string | number |
<optional> |
MIDI input device name, ID, or index to connect to |
Examples
// Basic device manager setup
const options = {
outputChannel: 1,
onStatusUpdate: (message, state) => updateStatusUI(message, state)
};
// Full device manager with UI integration
const options = {
output: "My Synth",
outputChannel: 2,
sysex: true,
selector: "[data-midi-cc]",
watchDOM: true,
onStatusUpdate: (message, state) => {
console.log(message);
document.getElementById("status").textContent = message;
},
onReady: async (midi, deviceManager) => {
// Setup device dropdown and channel selector
const deviceSelect = document.getElementById("device-select");
const channelSelect = document.getElementById("channel-select");
await deviceManager.setupSelectors({
output: deviceSelect,
channel: channelSelect
});
}
};
// With separate input/output channels
const options = {
inputChannel: 1,
outputChannel: 2,
input: "My MIDI Controller",
output: "My Synth",
onStatusUpdate: (message, state) => updateStatusUI(message, state)
};