Support delayed connection and reconnection of MIDI inputs
This commit is contained in:
parent
0c01a997ac
commit
dab1a018e4
1 changed files with 80 additions and 25 deletions
|
|
@ -59,9 +59,6 @@ export function enableWebMidi(options = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDevice(indexOrName, devices) {
|
function getDevice(indexOrName, devices) {
|
||||||
if (!devices.length) {
|
|
||||||
throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`);
|
|
||||||
}
|
|
||||||
if (typeof indexOrName === 'number') {
|
if (typeof indexOrName === 'number') {
|
||||||
return devices[indexOrName];
|
return devices[indexOrName];
|
||||||
}
|
}
|
||||||
|
|
@ -73,12 +70,13 @@ function getDevice(indexOrName, devices) {
|
||||||
const IACOutput = byName('IAC');
|
const IACOutput = byName('IAC');
|
||||||
const device = IACOutput ?? devices[0];
|
const device = IACOutput ?? devices[0];
|
||||||
if (!device) {
|
if (!device) {
|
||||||
throw new Error(
|
if (!devices.length) {
|
||||||
`🔌 MIDI device '${device ? device : ''}' not found. Use one of ${getMidiDeviceNamesString(devices)}`,
|
throw new Error(`🔌 No MIDI devices found. Connect a device or enable IAC Driver.`);
|
||||||
);
|
}
|
||||||
|
throw new Error(`🔌 Default MIDI device not found. Use one of ${getMidiDeviceNamesString(devices)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return IACOutput ?? devices[0];
|
return device;
|
||||||
}
|
}
|
||||||
|
|
||||||
// send start/stop messages to outputs when repl starts/stops
|
// send start/stop messages to outputs when repl starts/stops
|
||||||
|
|
@ -483,27 +481,15 @@ class MidiInput {
|
||||||
* @param {string | number} input MIDI device name or index defaulting to 0
|
* @param {string | number} input MIDI device name or index defaulting to 0
|
||||||
*/
|
*/
|
||||||
constructor(input) {
|
constructor(input) {
|
||||||
|
this.input = input;
|
||||||
this.stateKey = typeof input === 'string' ? input : undefined;
|
this.stateKey = typeof input === 'string' ? input : undefined;
|
||||||
|
|
||||||
const device = getDevice(input, WebMidi.inputs);
|
|
||||||
if (!device) {
|
|
||||||
throw new Error(
|
|
||||||
`midiin: device "${input}" not found.. connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._refs = {};
|
this._refs = {};
|
||||||
this._refsByChan = {};
|
this._refsByChan = {};
|
||||||
|
|
||||||
const midiListener = this._onMidiMessage.bind(this);
|
|
||||||
device.addListener('midimessage', midiListener);
|
|
||||||
|
|
||||||
this._loadAllStates();
|
this._loadAllStates();
|
||||||
|
|
||||||
const output = WebMidi.outputs.find((o) => o.name === device.name);
|
this.initialDevice = this._startDeviceListener();
|
||||||
if (output) {
|
|
||||||
this._sendAllStates(output);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createCC(cc, chan) {
|
createCC(cc, chan) {
|
||||||
|
|
@ -516,6 +502,71 @@ class MidiInput {
|
||||||
return ref(() => lookupMap[cc]);
|
return ref(() => lookupMap[cc]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_startDeviceListener() {
|
||||||
|
const initialDevice = getDevice(this.input, WebMidi.inputs);
|
||||||
|
if (!initialDevice) {
|
||||||
|
console.warn(`midiin: device "${this.input}" not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background connection loop
|
||||||
|
(async () => {
|
||||||
|
const midiListener = this._onMidiMessage.bind(this);
|
||||||
|
let device = initialDevice;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (!device) {
|
||||||
|
device = await this._waitForDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('midiin: device connected:', device.name);
|
||||||
|
|
||||||
|
// Wait a bit for device to be ready to receive last state
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const output = WebMidi.outputs.find((o) => o.name === device.name);
|
||||||
|
if (output) {
|
||||||
|
this._sendAllStates(output);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('midiin: failed to send last state on connect:', device.name, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for incoming MIDI messages and for disconnection
|
||||||
|
device.addListener('midimessage', midiListener);
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
const disconnectListener = (e) => {
|
||||||
|
if (e.port.name === device.name) {
|
||||||
|
console.warn('midiin: device disconnected:', device.name);
|
||||||
|
WebMidi.removeListener('disconnected', disconnectListener);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
WebMidi.addListener('disconnected', disconnectListener);
|
||||||
|
});
|
||||||
|
|
||||||
|
device = null; // Clear var to trigger wait for connection
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return initialDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
_waitForDevice() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const connListener = () => {
|
||||||
|
const device = getDevice(this.input, WebMidi.inputs);
|
||||||
|
if (device) {
|
||||||
|
WebMidi.removeListener('connected', connListener);
|
||||||
|
resolve(device);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
WebMidi.addListener('connected', connListener);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_onMidiMessage(e) {
|
_onMidiMessage(e) {
|
||||||
const ccNum = e.dataBytes[0];
|
const ccNum = e.dataBytes[0];
|
||||||
const v = e.dataBytes[1];
|
const v = e.dataBytes[1];
|
||||||
|
|
@ -622,11 +673,15 @@ export async function midin(input) {
|
||||||
midiInputs[input] = instance;
|
midiInputs[input] = instance;
|
||||||
|
|
||||||
if (initial) {
|
if (initial) {
|
||||||
const otherInputs = WebMidi.inputs.filter((o) => o.name !== instance.name);
|
const device = instance.initialDevice;
|
||||||
|
|
||||||
|
const otherInputs = WebMidi.inputs.filter((o) => o.name !== device.name);
|
||||||
logger(
|
logger(
|
||||||
`Midi enabled! Using "${instance.name}". ${
|
device
|
||||||
otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : ''
|
? `Midi enabled! Using "${device.name}". ${
|
||||||
}`,
|
otherInputs?.length ? `Also available: ${getMidiDeviceNamesString(otherInputs)}` : ''
|
||||||
|
}`
|
||||||
|
: `Midi enabled! Waiting for device "${input}"... Currently connected devices: ${getMidiDeviceNamesString(WebMidi.inputs)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue