jazz-soft/JZZ

How to temporarily stop midi input?

Avijobo opened this issue · 30 comments

Hi,

I am using jzz Web Midi API in node.js, however I can't find a way to stop midi input temporarily.

Closing the midi input ports does change the port.connection state to 'closed' but still keeps passing midi input messages to port.onmidimessage.
Calling navigator.close() stops the entire midi engine, but after that I cannot get it to work again: a new navigator.requestMIDIAccess() fails from then on.

Any ideas?

Looks like a bug... Thanks for letting me know! I'll take a look...

That was a subtle bug to spot. Thank you for finding it!
I have committed the fix, please get the latest release.
Now you can call close() on the input port to pause the input and open() to resume it.

Thanks, however now I get this error on my linux node server where JZZ is running:
W20200121-18:50:34.481(1)? (STDERR) TypeError: Cannot read property 'onstatechange' of undefined
W20200121-18:50:34.482(1)? (STDERR) at _statechange (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:2417:11)
W20200121-18:50:34.482(1)? (STDERR) at _R._wm_watch (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:2856:40)
W20200121-18:50:34.482(1)? (STDERR) at _fireW (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:560:83)
W20200121-18:50:34.483(1)? (STDERR) at _postRefresh (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:251:9)
W20200121-18:50:34.483(1)? (STDERR) at Object._engine._refresh (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:754:7)
W20200121-18:50:34.484(1)? (STDERR) at Timeout._onTimeout (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:859:64)
W20200121-18:50:34.484(1)? (STDERR) at listOnTimeout (internal/timers.js:531:17)
W20200121-18:50:34.484(1)? (STDERR) at processTimers (internal/timers.js:475:7)

I've committed some fixes.
Please copy the latest version to node_modules/jzz/javascript.
If your issue is fixed, I'll release a new version. Otherwise, I'll need your help to reproduce the bug.

Hi, I really appreciate your efforts, thanks a lot, however unfortunately it did not fix it. I occasionally get a "MIDI CALL IS NOT AVAILABLE" error now instead. It turns out I get this error without your latest fix either.

The context in which I use JZZ is part of a bigger Meteor project so I cannot easily isolate it.

This is what I basically do:
Incoming midi time code quarter frame messages are decoded, and as soon as a complete time code frame is received, the port is closed. It is then that I get above mentioned error, or a "MIDI CALL IS NOT AVAILABLE" error.

The goal is to reduce midi processing in node as much as possible when it is not needed, since I am only interested in occasionally monitoring incoming time code. So what I want to do is to turn off the midi port as soon as time code is received, an turn it on again a few seconds later.
As a workaround, instead of calling port.close() I change the port.onmidimessage callback to a dummy 'empty' callback for some time. This works, however being able to close the port (and reopen later) would save substantial more processing I assume.

The code is running on a Linux ubuntu server, and the incoming midi is coming as ipMidi from the network via qmidinet.

I don't think I have a "MIDI CALL IS NOT AVAILABLE" string anywhere in my code, but need to double check.

The most likely cause of the error is closing the port from within the onmidimessage handler. - I'll experiment with it tonite.

There are a couple solutions you can try:

  • delay the port.close() call via setTimer() etc...
  • use JZZ API directly instead of WebMIDI API - that will eliminate a complete JZZ-to-WebMIDI software layer with its resource consumption and possible bugs.

Does the program crash after printing "MIDI CALL IS NOT AVAILABLE" ?

Ok, I think I know where the error is...
Will try to rebuild the jazz-midi module during the weekend.

Please try v1.0.1. Hope it works...
Happy Chinese New Year! :)

Happy Chinese New Year too!

I hate to say this, but it still keeps crashing on Linux with JZZ v1.0.1. as soon as I call port.close():

W20200127-17:05:29.486(1)? (STDERR) TypeError: Cannot read property 'onstatechange' of undefined
W20200127-17:05:29.487(1)? (STDERR) at _statechange (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:2417:11)
W20200127-17:05:29.488(1)? (STDERR) at _R._wm_watch (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:2856:40)
W20200127-17:05:29.488(1)? (STDERR) at _fireW (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:560:83)
W20200127-17:05:29.488(1)? (STDERR) at _postRefresh (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:251:9)
W20200127-17:05:29.488(1)? (STDERR) at Object._engine._refresh (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:754:7)
W20200127-17:05:29.489(1)? (STDERR) at Timeout._onTimeout (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:859:64)
W20200127-17:05:29.489(1)? (STDERR) at listOnTimeout (internal/timers.js:531:17)
W20200127-17:05:29.489(1)? (STDERR) at processTimers (internal/timers.js:475:7)

I don't seem to get the 'MIDI CALL IS NOT AVAILABLE' anymore though.

The weird thing is that in Windows I don't have this error/crash. FYI both my Linux and Windows systems have node v12.14.0 installed, and receive the same ipMidi messages.

This output looks weird... It says that a MIDI-Out port is disconnected.
Do you have any ideas how that can happen?

No, I don't use midi-out ports. This is my code (Typescript):

`export class MidiHandler {
static instances = 0;

private midiAccess: WebMidi.MIDIAccess;
private midiInputCallback: (midiMsgEvent: WebMidi.MIDIMessageEvent) => void;

//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static create(navigator: Navigator, sysex: boolean): MidiHandler {
	if (MidiHandler.instances) return;	// Force singleton

	if (navigator.requestMIDIAccess) {	// MIdi access supported?
		MidiHandler.instances++;
		return new MidiHandler(navigator, sysex);
	}
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
private constructor(navigator: Navigator, sysex: boolean) {
	navigator.requestMIDIAccess({ sysex: sysex }).then((midiAccess: WebMidi.MIDIAccess) => {
		this.midiAccess = midiAccess;

		this.midiAccess.inputs.forEach((port: WebMidi.MIDIInput, id: string) => {
			let log = Meteor.isServer ? console.log : screenLog;
			log("midiinput id = " + id + ", port name = " + port.name);
			if (this.midiInputCallback)
				port.onmidimessage = this.midiInputCallback; // Automatically opens port
		});
	});
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setMidiInputCallback(callback: (midiMsgEvent: WebMidi.MIDIMessageEvent) => void) {
	this.midiInputCallback = callback && Meteor.bindEnvironment(callback);

	if (this.midiAccess)
		this.midiAccess.inputs.forEach((port: WebMidi.MIDIInput, id: string) => {
			if (this.midiInputCallback) {
				port.onmidimessage = this.midiInputCallback;	// Automatically opens port if not open yet
				console.log(port.name + " port.state = " + port.state + ", port.connection = " + port.connection);
			}
		});
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
closeMidiInput() {
	if (this.midiAccess)
		this.midiAccess.inputs.forEach((port: WebMidi.MIDIInput, id: string) => {
			port.onmidimessage = () => {}; // Dummy do-nothing callback;
			//////port.close();
		});
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
openMidiInput() {
	if (this.midiAccess)
		this.midiAccess.inputs.forEach((port: WebMidi.MIDIInput, id: string) => {
			port.open();
		});
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

}`

Can you please add the following output before the line #2854 in JZZ.js:
console.log('Disconnected MIDI-Out:', p.id);
hope it will give us some ideas...

This is the result (I got 2 midi ports, which I both close. I tried earlier with 1 port, same result):

I20200130-09:11:25.520(1)? Disconnected MIDI_Out
I20200130-09:11:25.520(1)? Disconnected MIDI_Out
W20200130-09:11:25.523(1)? (STDERR) TypeError: Cannot read property 'onstatechange' of undefined
W20200130-09:11:25.523(1)? (STDERR) at _statechange (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:2417:11)
W20200130-09:11:25.524(1)? (STDERR) at _R._wm_watch (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:2857:40)
W20200130-09:11:25.524(1)? (STDERR) at _fireW (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:560:83)
W20200130-09:11:25.524(1)? (STDERR) at _postRefresh (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:251:9)
W20200130-09:11:25.525(1)? (STDERR) at Object._engine._refresh (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:754:7)
W20200130-09:11:25.525(1)? (STDERR) at Timeout._onTimeout (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:859:64)
W20200130-09:11:25.525(1)? (STDERR) at listOnTimeout (internal/timers.js:531:17)
W20200130-09:11:25.525(1)? (STDERR) at processTimers (internal/timers.js:475:7)

Does that means the MIDI-Out port name is empty?

Oops... here's the correct log:

I20200130-18:11:01.426(1)? Disconnected MIDI-Out: my_port
I20200130-18:11:01.427(1)? Disconnected MIDI-Out: my_port
W20200130-18:11:01.429(1)? (STDERR) TypeError: Cannot read property 'onstatechange' of undefined
W20200130-18:11:01.430(1)? (STDERR) at _statechange (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:2417:11)
W20200130-18:11:01.430(1)? (STDERR) at _R._wm_watch (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:2857:40)
W20200130-18:11:01.431(1)? (STDERR) at _fireW (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:560:83)
W20200130-18:11:01.431(1)? (STDERR) at _postRefresh (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:251:9)
W20200130-18:11:01.431(1)? (STDERR) at Object._engine._refresh (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:754:7)
W20200130-18:11:01.432(1)? (STDERR) at Timeout._onTimeout (/home/johan/Meteor/TestProject/node_modules/jzz/javascript/JZZ.js:859:64)
W20200130-18:11:01.432(1)? (STDERR) at listOnTimeout (internal/timers.js:531:17)
W20200130-18:11:01.432(1)? (STDERR) at processTimers (internal/timers.js:475:7)

What kind of device is that?
What are the names of other input/output devices?

It is a virtual midi driver for linux: qmidinet, which uses the 'ipMidi' protocol, allowing midi connections between computers via ethernet on a LAN.
These are all ports on the system:

I20200130-20:45:40.363(1)? midi input id = Midi Through Port-0, port name = Midi Through Port-0
I20200130-20:45:40.384(1)? midi input id = port 0, port name = port 0
I20200130-20:45:40.519(1)? midi output id = Midi Through Port-0, port name = Midi Through Port-0
I20200130-20:45:40.520(1)? midi output id = port 0, port name = port 0
I20200130-20:45:40.521(1)? midi output id = my_port, port name = my_port

The last midi output port 'my_port' is indeed a weird one, which I haven't defined.
I'll see if I can update qmidinet to its latest version, will be something for tomorrow...

Do you disconnect that MIDI port before the crash occurs?
Or, how does it get disconnected?

You just close the ports, you don't disconnect the ports form your system, correct?

There is one more hack you can try - enumerate all MIDI outputs:
midiAccess.outputs.forEach(call_some_dummy_function);
and let me know if it helps...

That didn't help either.

I did found something though:
When I launch qmidinet to create e.g. 2 virtual ports, it seems that an additional (temporary?) 3rd midi out port called "my_port" is being created. If I then open these 2 midi in ports, and then later close both of them, then this additional out port is being closed too, and this causes the program to crash.

The only way to avoid a crash is to open (and close) only 1 of the 2 input ports at a time. I tried this earlier too, before your last fix, but then I got a "MIDI CALL NOT AVAILABLE" error in that case. This is not occurring anymore now, thanks!

However when I close this 1 input port I now still occasionally get a console "Disconnected MIDI-Out: my_port" log coming from the console.log() line that you asked me to add at line #2854 in JZZ.js. Apart from this log, everything seems to work fine then.

So I would think the whole problem is related to a bug in qmidinet that generates a half-baked "my_port" that appears to be a midi out port, but seems to interfere with midi in ports as well? Anyway, I can live with using only 1 port now... I am just not feeling very comfortable with this occasional "Disconnected MIDI-Out: my_port" log that show up, so I am still hesitating to use my original workaround with a dummy callback instead of closing the midi in port.

Anyway, let me thank you once more for the time to look into this! Unfortunately (or fortunately?) the problem comes from elsewhere I think.

Thanks! There is a lot of valuable information in your latest comment!
Apparently, JZZ crashes when it gets the same "disconnected port" event twice.
I'll see what I can do about it... Stay tuned :)

Please try v1.0.2

That works!!! Can't get it to crash anymore now, even opening/closing all ports including "my_port" doesn't do any harm anymore.... works very nice now, thanks a million! I really appreciate the effort you did here!

Happy to hear that!
:)