-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Disconnect/Reconnect events? #12
Comments
Doing a bit of research, it seems like rtmidi doesn't provide disconnect/reconnect events so any implementation would need to utilise polling which could be achieved just as well in your code (ref micdah/RtMidi.Core#18) I am open to this library being extended with an implementation of that to avoid everyone having to reimplement it, but it needs someone to implement it |
Warning!don't blindly use the following code, as it's not really production ready yet (it's a PoC) and needs cleanup:I just made a base class for that. const MidiDevice = class {
deviceName = "";
input = new midi.Input();
output = new midi.Output();
controls = new Map();
ticks = new Map();
contexts = new Map();
tickContext = {};
deltaTime = 0;
#contextTemplate = null;
constructor(deviceName, ctx) {
this.deviceName = deviceName;
this.#contextTemplate = function(ctx) {
if (typeof ctx === "object") Object.assign(this, ctx);
};
this.#contextTemplate.prototype = {
self: this,
tick: this.tickContext,
};
if (typeof ctx === "object") Object.assign(this.#contextTemplate.prototype, ctx);
this.input.on("message", (deltaTime, message) => {
this.control(deltaTime, message);
});
}
#checkPorts() {
const { input, output } = this;
const inC = input.getPortCount();
const outC = output.getPortCount();
const inConnected = input.isPortOpen();
const outConnected = output.isPortOpen();
let inPort = -1;
for (let i = 0; i < inC; i++) {
if (input.getPortName(i) === this.deviceName) {
inPort = i;
break;
}
}
let outPort = -1;
for (let i = 0; i < outC; i++) {
if (output.getPortName(i) === this.deviceName) {
outPort = i;
break;
}
}
if (inPort < 0 && inConnected) {
console.log(`device ${this.deviceName} input port is open but not found. disconnecting`);
input.closePort();
this.tickContext.ev = "closeInput";
if (this.ticks.has("closeInput")) this.ticks.get("closeInput").call(this.tickContext);
delete this.tickContext.ev;
}
else if (inPort >= 0 && !inConnected) {
console.log(`device ${this.deviceName} input port is closed but found. connecting`);
input.openPort(inPort);
this.tickContext.ev = "openInput";
if (this.ticks.has("openInput")) this.ticks.get("openInput").call(this.tickContext);
delete this.tickContext.ev;
}
if (outPort < 0 && outConnected) {
console.log(`device ${this.deviceName} output port is open but not found. disconnecting`);
output.closePort();
this.tickContext.ev = "closeOutput";
if (this.ticks.has("closeOutput")) this.ticks.get("closeOutput").call(this.tickContext);
delete this.tickContext.ev;
}
else if (outPort >= 0 && !outConnected) {
console.log(`device ${this.deviceName} output port is closed but found. connecting`);
output.openPort(outPort);
this.tickContext.ev = "openOutput";
if (this.ticks.has("openOutput")) this.ticks.get("openOutput").call(this.tickContext);
delete this.tickContext.ev;
}
}
#debugVerbose = false;
#debugEnabled = false;
debug(verbose = false) {
const self = this;
this.#debugVerbose = verbose;
this.#debugEnabled = true;
this.addControl("debug", function ({status, control, value}) {
console.log(`debug: ${self.deviceName} ${status} ${control} ${value} ${JSON.stringify(this, null, 2)} ${JSON.stringify(this.tick, null, 2)}`);
});
}
#addThing(type, control, callback, ctx) {
this[type].set(control, callback);
if (!this.contexts.has(control)) this.contexts.set(control, new this.#contextTemplate(ctx));
else if (typeof ctx === "object") Object.assign(this.contexts.get(control), ctx);
}
addControl(...args) {
this.#addThing("controls", ...args);
return this;
}
addTick(...args) {
this.#addThing("ticks", ...args);
return this;
}
#removeThing(type, control) {
this[type].delete(control);
if (!this.ticks.has(control)) this.contexts.delete(control);
}
removeControl(...args) {
this.#removeThing("controls", ...args);
return this;
}
removeTick(...args) {
this.#removeThing("ticks", ...args);
return this;
}
tick() {
this.#checkPorts();
this.ticks.forEach((callback, key) => {
callback.call(this.contexts.get(key));
});
}
control(deltaTime, message) {
const [status, control, value] = message;
const hasControl = this.controls.has(control);
const hasDebug = this.controls.has("debug");
if (!hasControl && !hasDebug) return;
const identifier = hasControl ? control : "debug";
const context = this.contexts.get(identifier);
const callback = this.controls.get(identifier);
const now = new Date();
if (!("lastEvent" in context)) {
context.lastEvent = now;
}
context.deltaTime = (now - context.lastEvent) / 1000;
context.lastEvent = now;
context.status = status;
context.control = control;
context.value = value;
this.deltaTime = deltaTime;
if (this.#debugEnabled && (this.#debugVerbose || !hasControl)) {
this.controls.get("debug").call(context, {status, control, value});
}
if (hasControl) callback.call(context, {status, control, value});
}
}; it can then be used like this: const trinket = new class extends MidiDevice {
constructor() {
super("CircuitPython Audio");
const self = this;
/*// increase voicemeeter volume on control 1
this.addControl(1, function ({value}) {
if (value === 0) return;
const gain = vm.api["Bus[0].Gain"] += 4;
console.log(`setting voicemeeter gain to ${gain.toFixed(3)} dB`);
});
// decrease voicemeeter volume on control 2
this.addControl(2, function ({value}) {
if (value === 0) return;
const gain = vm.api["Bus[0].Gain"] -= 4;
console.log(`setting voicemeeter gain to ${gain.toFixed(3)} dB`);
});*/
// mute mic on control 3
this.addTick("openOutput", function () {
if (!ctx.vmdirty && this.ev !== "openOutput") return;
const isMicMuted = vm.raw["Strip[0].Mute"];
self.output.sendMessage([176, 0b0110001, isMicMuted ? 1 : 0]);
self.output.sendMessage([176, 0b0110100, isMicMuted ? 0 : 1]);
const isMainMuted = vm.raw["Bus[0].Mute"];
self.output.sendMessage([176, 0b1000001, isMainMuted ? 1 : 0]);
self.output.sendMessage([176, 0b1000010, isMainMuted ? 0 : 1]);
const isHeadsetMuted = vm.raw["Bus[3].Mute"];
self.output.sendMessage([176, 0b0001001, isHeadsetMuted ? 1 : 0]);
self.output.sendMessage([176, 0b0001010, isHeadsetMuted ? 0 : 1]);
});
this.addControl(3, function ({value}) {
if (value === 0 && this.deltaTime < 0.15) return;
const isMuted = vm.raw["Strip[0].Mute"] ^= 1;
console.log(`setting voicemeeter mic mute to ${isMuted ? "muted" : "unmuted"}`);
self.output.sendMessage([176, 0b0110001, isMuted ? 1 : 0]);
self.output.sendMessage([176, 0b0110100, isMuted ? 0 : 1]);
});
}
} i have made it quite usable tbh.. // gain levels
for (let [i, bus] of sequence(4, 4)) {
this.addControl(i, function ({value}) {
// ToDo: debounce
const gain = gains[value];
console.log(`setting bus ${bus} gain for ${self.busNames.get(bus)} to ${gain.toFixed(3)} dB`);
vm.raw[`Bus[${bus}].Gain`] = gain;
});
} all you then need to do, is to call let tick = 0;
while(true) {
tick++;
try {
const vmdirty = ctx.vmdirty = vm.isParametersDirty();
if (vmdirty) vm.updateDeviceList();
nanoKONTROL2.tick();
trinket.tick();
pr0Board.tick();
} catch (e) {
console.log("such e", e);
}
await sleep(100);
} I'm currently re-designing the voicemeeter-remote module.. const isMuted = vm.raw["Strip[0].Mute"] ^= 1; while in future, it might just be: const isMuted = vm.Strip[0].Mute.$ ^= 1; when I'm done, I'll likely refine my node-midi-wrapper and post it as frontend for this module with such functions. |
I've been looking into this too. It seems that on Linux+ALSA one could launch I think it would be best if it was possible to subscribe to the Announce port via node-midi instead of launching a separate process and parsing its output stream. |
Here's a simple example of an EventEmitter that emits ALSA client connect / exit events: import { EventEmitter } from 'node:events';
import { spawn } from 'node:child_process';
export const alsaClientEvents = new EventEmitter;
const childProcess = spawn( 'aseqdump', [ '-p', '0:1' ]);
childProcess.stdout.on( 'data', data => {
const content = data.toString();
if( content.includes( 'Client exit' )){
alsaClientEvents.emit( 'exit', content );
} else if( content.includes( 'Client start' )){
alsaClientEvents.emit( 'start', content );
}
}); |
Hi,
There is a post on the original node-midi repository.
justinlatimer#226
Is there a way to do that with this version?
Thanks
The text was updated successfully, but these errors were encountered: