Skip to content
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

Feature request: Auto-reconnect device #371

Open
elgiano opened this issue Jun 15, 2020 · 12 comments
Open

Feature request: Auto-reconnect device #371

elgiano opened this issue Jun 15, 2020 · 12 comments

Comments

@elgiano
Copy link
Contributor

elgiano commented Jun 15, 2020

It would be great to have an auto-reconnect feature, in case of accidental unplug of a device during a performance.

Here is how I see it could be done:

  • an external "poller" polls midi sources on a regular basis. MKtl optionally registers connected devices to it, so that if one of them disappears and then re-appears, the poller calls MKtl(x).reconnect.
  • MKtl(x).reconnect could just trash the previous device and make a new one ( try{this.closeDevice}; this.device = nil; this.openDevice )
@LFSaw
Copy link
Member

LFSaw commented Jun 24, 2020

MIDI does usually "just" reconnect (on OSX). Which devices are you thinking of?

@elgiano
Copy link
Contributor Author

elgiano commented Jun 24, 2020

MIDI. On linux I have to set up automatic reconnection through another program, something like qjackctl's patchbays.
I thought it could have been specific to a MIDI device, so I tried with a couple of others... and no, it doesn't reconnect on linux.

// device is plugged
MIDIClient.init;
MIDIIn.connectAll;
MIDIdef.cc(\test){|...args| args.postln};
// getting messages
// now unplug/replug: not getting messages anymore

However, there is something specific to the pedalboard I'm using, which is that when I plug it in, I have to send a special sysex for it to be in "hosted" mode.

@elgiano
Copy link
Contributor Author

elgiano commented Jun 24, 2020

I want to add that what currently blocks me from implementing this separately is #372. I posted the present feature request because I thought it could be an interesting feature to add to Modality, not knowing that on mac it "just" works :)

@LFSaw
Copy link
Member

LFSaw commented Jun 28, 2020

did not intend to bully, just tried to understand what's wrong...

@elgiano
Copy link
Contributor Author

elgiano commented Jun 28, 2020

Opssss some communication didnt work right here! I didn't intend to sound pissed, and havent felt bullied at all! Sorry if yiu got that impression, and thanks for your work and replies!

@LFSaw
Copy link
Member

LFSaw commented Jun 28, 2020

no worries :)
As for your request, I am currently with my head in other projects (and languages), so I am not the right person to talk to when it comes to implementation details... I guess @adcxyz knows better about these issues...

@adcxyz
Copy link
Contributor

adcxyz commented Jun 28, 2020

Just tested on macOS, can confirm what @LFSaw said, plugging and unplugging a device just magically reconnects.
@elgiano, this would have to be linux specific, so maybe you can try to write it and submit a PR.

@adcxyz
Copy link
Contributor

adcxyz commented Jun 28, 2020

hm, this should actually be in SC itself - it reconnects without modality already:

// plug in a MIDI device to test, then:
MIDIClient.init;
MIDIIn.connectAll;
// listen to all midi cc messages:
MIDIdef.cc(\x, { |val, num, chan| [val, num, chan].postln });
/// now unplug the device ...
/// then plug it back in, and wiggle a controller:
/// reconnects automagically on macOS

@elgiano
Copy link
Contributor Author

elgiano commented Jul 2, 2020

I can confirm that automagic reconnection doesn't happen on Linux without an external program doing it.
On mac, I guess it's macosx itself doing it, or at least sc's coremidi interface. One thing that would be nice to know is whether, when you plug in a new device after MIDIIn.connectAll, that device shows up in MIDIClient.sources/destinations or not. It doesn't on Linux until you call MIDIClient.list.

I think this is then out of Modality's scope. If such a feature would be implemented in SC, maybe Modality could have some hooks to it, but probably that wouldn't really be needed anymore. So feel free to close this issue :)

If anyone is interested, I sketched a (working :)) Linux implementation, that extends SC_AlsaMIDI's processEvent() to forward port-related messages to sclang, so that MIDIFunc/defs can respond to them:
https://github.com/elgiano/supercollider/tree/feature/midi-watch-client

Thanks for your collaboration!

@adcxyz
Copy link
Contributor

adcxyz commented Jul 6, 2020

I can confirm that automagic reconnection doesn't happen on Linux without an external program doing it.
On mac, I guess it's macosx itself doing it, or at least sc's coremidi interface. One thing that would be nice to know is whether, when you plug in a new device after MIDIIn.connectAll, that device shows up in MIDIClient.sources/destinations or not. It doesn't on Linux until you call MIDIClient.list.

Newly connected devices do not show up until you call MIDIClient.init:

MIDIClient.list; MIDIClient.sources.printAll; MIDIClient.destinations.printAll; "";

and there is no automatic system notification when a new device was connected.

I think this is then out of Modality's scope. If such a feature would be implemented in SC, maybe Modality could have some hooks to it, but probably that wouldn't really be needed anymore. So feel free to close this issue :)

OK, will do when we know that is the way to go.

If anyone is interested, I sketched a (working :)) Linux implementation, that extends SC_AlsaMIDI's processEvent() to forward port-related messages to sclang, so that MIDIFunc/defs can respond to them:
https://github.com/elgiano/supercollider/tree/feature/midi-watch-client

I checked, polling MIDIClient.prList is really cheap,
so one could do a little polling Updater class - sketch:

+ MIDIUpdater { 
	classvar <skipJack;
	classvar <>onSourceAdded, <>onSourceRemoved; 
	classvar <>onDestAdded, <>onDestRemoved;
	
	*run { |dt|
		if (skipJack.isNil) {
			skipJack = SkipJack({ 
				this.checkUpdate
			}, 2, false, this.name, AppClock, false);
		};
		if (dt.notNil) { skipJack.dt = dt };
		skipJack.play;
	}
	
	*stop { skipJack.stop }
	
	*checkUpdate { 
		// get new list
		var rawlists = MIDIClient.prList.clump(3);
		// split into newSources, newDests;
		var newSourceSpecs = rawlists[0].flop; 
		var newDestSpecs = rawlists[1].flop;
		
		// compare with old list, and only if the lists are different, do: 
		// update sources & dests
		// if new source was added, onSourceAdded.(new source);
		// if source was removed, onSourceAdded.(removed source);
		// if new dest was added, onDestAdded.(new dest);
		// if dest was removed, onDestRemoved.(removed dest);
	}
}

@elgiano
Copy link
Contributor Author

elgiano commented Jul 6, 2020

I also thought about polling initially, then my problem was that if you disconnect and reconnect a device quicker than the poll rate, you loose that event, and that would be meaningful to me in case I have to call a function every time a device is connected (e.g. setting the device in "hosted" mode by sending a sysex).

The advantage of a poller though, is that it would be automatically cross-platform, leaving communication with the specific midi driver as a responsibility for a lower level...

@adcxyz
Copy link
Contributor

adcxyz commented Jul 16, 2020

@elgiano - do you have any numbers how short these dropouts can get?
If the poller is cheap, and the poll rate can be set individually,
you can tweak the rate to be fast enough for all likely cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants