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

Use unique port identifiers #32

Closed
Boddlnagg opened this issue Dec 27, 2017 · 12 comments
Closed

Use unique port identifiers #32

Boddlnagg opened this issue Dec 27, 2017 · 12 comments
Assignees
Milestone

Comments

@Boddlnagg
Copy link
Owner

Boddlnagg commented Dec 27, 2017

Ports should not be identified by index (for calls to port_name and connect), but rather by some backend-specific identifier that does not change when the order of devices changes.

These identifiers do not need to be persistent across sessions (e.g. to write them to a configuration file), because that would be quite a bit harder and can be added later.

The following identifiers could work for each platform:

Backend Port Identifier
ALSA snd_seq_addr_t (numeric client + port IDs)
CoreMIDI MIDIEndpointRef, or maybe MIDIObjectFindByUniqueID
JACK Port name (string)
WinMM Maybe a combination of port name (as returned by GetDevCaps) and interface name (another string, see https://docs.microsoft.com/de-de/windows-hardware/drivers/audio/obtaining-a-device-interface-name). This requires a linear search when calling connect(), but I think that's not a problem.
WinRT DeviceInformation::Id (string)

The platform-specific representation could be wrapped in a PortId struct.

@Boddlnagg Boddlnagg changed the title Use unique device identifiers Use unique port identifiers Dec 27, 2017
@Boddlnagg Boddlnagg self-assigned this Dec 28, 2017
@Boscop
Copy link

Boscop commented Jan 16, 2018

Why is it not enough to identify ports by names? :)

What I always do is find the port by name first, before calling connect(), so in my app config I store only the port name...

pub fn open_input(port_name: &str) -> Result<(MidiInputConnection<()>, Receiver<ChanMsg>), Box<Error + Send + Sync>> {
	let mut midi = MidiInput::new("")?;
	midi.ignore(Ignore::None); // All
	let port = (|| {
		for i in 0..midi.port_count() {
			if let Ok(name) = midi.port_name(i) {
				if name == port_name {
					return Some(i);
				}
			}
		}
		return None;
	})().ok_or(format!("midi input port '{}' not found", port_name))?;

	let (tx_midi_to_daw, rx_midi_to_daw) = channel();
	Ok((midi.connect(port, "", move |_stamp, msg, _| {
		if let Some(msg) = LowLvlMsg::parse(msg) {
			tx_midi_to_daw.send(ChanMsg::from(msg)).unwrap();
		}
	}, ()).map_err(|e| e.kind())?, rx_midi_to_daw))
}

pub fn open_output(port_name: &str) -> Result<MidiOutputConnection, Box<Error + Send + Sync>> {
	let midi = MidiOutput::new("")?;
	let port = (|| {
		for i in 0..midi.port_count() {
			if let Ok(name) = midi.port_name(i) {
				if name == port_name {
					return Some(i);
				}
			}
		}
		return None;
	})().ok_or(format!("midi output port '{}' not found", port_name))?;

	Ok(midi.connect(port, "").map_err(|e| e.kind())?)
}

@Boddlnagg
Copy link
Owner Author

@Boscop Because multiple devices of the same type may have the same name. 😞 There are better ways to identify ports, as you can see in the table above, but they differ by backend, and therefore need to be wrapped in a cross-platform way. I have a prototype implementation for Windows in https://github.com/Boddlnagg/midir/tree/port-ids, if you want to see how this looks (have a look at the changes in src/common.rs to see the API changes).

For configuration you can store the port name ... there's not really a good way to deal with multiple devices of the same name/type, because their internal device ID will change when they are plugged out and back in again. But that is tracked by #34.

@Boddlnagg
Copy link
Owner Author

Status update: The port-ids branch contains an implementation of this change for all backends except CoreMIDI (which I didn't have the time to do yet).

@Boddlnagg Boddlnagg added this to the 0.6.0 milestone Feb 12, 2019
@Boddlnagg
Copy link
Owner Author

Implemented in #38

@Boscop
Copy link

Boscop commented May 11, 2020

@Boddlnagg I just updated my midir dep from 0.3 to 0.5. So what is different exactly regarding ports? The connect function still has the same signature.
What should I pass as port_name argument? I've always passed "" and the port number that I got from finding the port number in the list of available ports based on my app's cfg.port_name.
Does connect nowadays use the port_name argument to find the right port to connect to? So if I pass "" it won't work?

Btw, now I'm starting to use multiple midi controllers of the same kind, who all have the same port names, so I need to extend my config to be able to specify which one is meant (e.g. I could add a nth param, as in "choose the nth one with name", would that be recommended?).
How does this PR relate to this use case? :)

@Boddlnagg
Copy link
Owner Author

Actually, version 0.5 does not yet contain this change. Don't ask me why, but there hasn't been a release for a long while now. I'll try to release a new version soon™.

You can try to work with a git dependency directly if you want to try it out before that.

@Boddlnagg
Copy link
Owner Author

Btw, now I'm starting to use multiple midi controllers of the same kind, who all have the same port names, so I need to extend my config to be able to specify which one is meant (e.g. I could add a nth param, as in "choose the nth one with name", would that be recommended?).
How does this PR relate to this use case? :)

It helps, because you can now implement that logic to "choose thenth one with name". It would help even more if we had #34 ...

@Boddlnagg
Copy link
Owner Author

I just published version 0.6.0, containing this change!

@Boddlnagg Boddlnagg reopened this May 11, 2020
@Boscop
Copy link

Boscop commented May 12, 2020

@Boddlnagg Thanks. Btw, how exactly are these new identifiers different?
I could also implement the "choose the nth one with name" logic with v0.5.
What other advantage does this change have?

And another question about the port_name arg to connect():

The port_name is an additional name that will be assigned to the connection. It is only used by some backends.

What should I pass as the name and what exactly is it used for? For port identification during connection? I always passed "" and it worked (I'm on Windows).

@Boddlnagg
Copy link
Owner Author

I could also implement the "choose the nth one with name" logic with v0.5.
What other advantage does this change have?

Yeah, sorry, it's correct that you could have implemented that with v0.5. What's handled better now is the case where you display the devices in a list and let the user select one, then connect to the selected one. By the time the selection has happened, a device might have been removed so that the indices are shifted, and then the selection would be applied incorrectly.

This solution is bascially what thestk/rtmidi#30 tries for RtMidi.

What should I pass as the name and what exactly is it used for? For port identification during connection? I always passed "" and it worked (I'm on Windows).

It's not used on Windows, but with the ALSA, CoreMIDI and Jack backends, ports in the backend actually have names, which are sometimes displayed to the user. For example, for ALSA it's passed to snd_seq_port_info_set_name and for CoreMIDI to MIDIInputPortCreate. I think it's fine to use an empty string if you don't care.

@Boscop
Copy link

Boscop commented May 13, 2020

@Boddlnagg Thanks! I have multi-device operation working now. Btw, do you know if it's always the case that "when multiple midi devices of the same model are connected simultaneously, the relative order between them is the same in the list of input and output ports (as returned by midir)"?
For example, if the input ports returned by midir are A, B, C, B, C and output ports are B, C, E, B, F, can I just assume that the nth B in input ports is from the same device as the nth B in the list of output ports?
(I'm referring to midi devices that have input AND ouput ports, such as BCR2000 and Launchpad. And they don't just have 1 input and 1 output port each.)
When I assumed this, it worked, but I'm not sure if it's valid to assume that.

Are midi ports always appended at the end of the OS's port list when the device gets plugged in?

@Boddlnagg
Copy link
Owner Author

Boddlnagg commented May 13, 2020

Btw, do you know if it's always the case that "when multiple midi devices of the same model are connected simultaneously, the relative order between them is the same in the list of input and output ports (as returned by midir)"?
...
Are midi ports always appended at the end of the OS's port list when the device gets plugged in?

I can't really answer these questions, since it depends on the respective platform backends. It might be that some or even all platforms behave that way, but I really don't know it. I guess you would have to look into the OS/platform documentation, and honestly I doubt that this is documented at all ... at least I haven't come across any statement regarding the order of ports while reading platform documentation ... sorry 😕

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

No branches or pull requests

2 participants