1 /*
2  * Copyright (C) 2014-2019 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18 
19 #include <alsa/asoundlib.h>
20 #include <glib.h>
21 
22 #include "pbd/string_convert.h"
23 #include "ardouralsautil/devicelist.h"
24 
25 using namespace std;
26 
27 void
get_alsa_audio_device_names(std::map<std::string,std::string> & devices,AlsaDuplex duplex)28 ARDOUR::get_alsa_audio_device_names (std::map<std::string, std::string>& devices, AlsaDuplex duplex)
29 {
30 	snd_ctl_t *handle;
31 	snd_ctl_card_info_t *info;
32 	snd_pcm_info_t *pcminfo;
33 	snd_ctl_card_info_alloca(&info);
34 	snd_pcm_info_alloca(&pcminfo);
35 	string devname;
36 	int cardnum = -1;
37 	int device = -1;
38 	const char* fixed_name;
39 
40 	if ((fixed_name = g_getenv ("ARDOUR_ALSA_DEVICE"))) {
41 		devices.insert (make_pair<string,string> (fixed_name, fixed_name));
42 		return;
43 	}
44 
45 	assert (duplex > 0);
46 
47 	while (snd_card_next (&cardnum) >= 0 && cardnum >= 0) {
48 
49 		devname = "hw:";
50 		devname += PBD::to_string (cardnum);
51 
52 		if (snd_ctl_open (&handle, devname.c_str(), 0) >= 0 && snd_ctl_card_info (handle, info) >= 0) {
53 
54 			if (snd_ctl_card_info (handle, info) < 0) {
55 				continue;
56 			}
57 
58 			string card_name = snd_ctl_card_info_get_name (info);
59 			bool have_multiple_subdevices = false;
60 
61 			while (snd_ctl_pcm_next_device (handle, &device) >= 0 && device >= 0) {
62 
63 				/* only detect duplex devices here. more
64 				 * complex arrangements are beyond our scope
65 				 */
66 
67 				snd_pcm_info_set_device (pcminfo, device);
68 				snd_pcm_info_set_subdevice (pcminfo, 0);
69 				snd_pcm_info_set_stream (pcminfo, SND_PCM_STREAM_CAPTURE);
70 
71 				if (snd_ctl_pcm_info (handle, pcminfo) < 0 && (duplex & HalfDuplexIn)) {
72 					continue;
73 				}
74 
75 				snd_pcm_info_set_device (pcminfo, device);
76 				snd_pcm_info_set_subdevice (pcminfo, 0);
77 				snd_pcm_info_set_stream (pcminfo, SND_PCM_STREAM_PLAYBACK);
78 
79 				if (snd_ctl_pcm_info (handle, pcminfo) < 0 && (duplex & HalfDuplexOut)) {
80 					continue;
81 				}
82 
83 				/* prefer hardware ID (not card/device number) */
84 				string hwname = "hw:";
85 				hwname += snd_ctl_card_info_get_id (info);
86 				hwname += ',';
87 				hwname += PBD::to_string (device);
88 
89 				if (false /* list first subdevice only */) {
90 					devices.insert (std::make_pair (card_name, hwname));
91 					continue;
92 				}
93 
94 				string uniq_name = card_name;
95 
96 				if (have_multiple_subdevices) {
97 					uniq_name += " (" + hwname + ")";
98 				}
99 
100 				std::pair<std::map<std::string, std::string>::iterator, bool> rv;
101 				rv = devices.insert (std::make_pair (uniq_name, hwname));
102 
103 				if (!rv.second) {
104 					assert (!have_multiple_subdevices);
105 					have_multiple_subdevices = true;
106 
107 					uniq_name += " (" + PBD::to_string (device) + ")";
108 					devices.insert (std::make_pair (uniq_name, hwname));
109 #if 0 // disabled (blame the_CLA's laptop)
110 					/* It may happen that the soundcard has multiple sub-devices for playback
111 					 * but none for recording.
112 					 *
113 					 * In that case the playback device-name has a suffix "(0)" while
114 					 * the capture device has none.
115 					 *
116 					 * This causes issues for backends that use
117 					 *  ::match_input_output_devices_or_none()
118 					 *
119 					 * (the alternative would be to always add a suffix,
120 					 * and the proper solution would be to compare the hw:name)
121 					 */
122 					/* remname the previous entry */
123 					hwname = devices[card_name];
124 					devices.erase (devices.find (card_name));
125 					size_t se = hwname.find_last_of (',');
126 					assert (se != string::npos);
127 
128 					uniq_name = card_name + " (" + hwname.substr (se + 1) + ")";
129 					devices.insert (std::make_pair (uniq_name, hwname));
130 #endif
131 				}
132 			}
133 
134 			snd_ctl_close(handle);
135 		}
136 	}
137 }
138 
139 static void
insert_unique_device_name(std::map<std::string,std::string> & devices,std::string const & card_name,std::string const & devname,int caps)140 insert_unique_device_name (std::map<std::string, std::string>& devices, std::string const& card_name, std::string const& devname, int caps)
141 {
142 	assert (caps != 0);
143 	std::pair<std::map<std::string, std::string>::iterator, bool> rv;
144 	char cnt = '2';
145 	std::string cn = card_name;
146 	/* Add numbers first this is be independent of physical ID (sequencer vs rawmidi).
147 	 * If this fails (>= 10 devices) add the device-name for uniqness
148 	 *
149 	 * XXX: Perhaps this is a bad idea, and `devname` should always be added if
150 	 * there is more than one device with the same name.
151 	 */
152 	do {
153 		cn += " (";
154 		if (caps & SND_SEQ_PORT_CAP_READ) cn += "I";
155 		if (caps & SND_SEQ_PORT_CAP_WRITE) cn += "O";
156 		cn += ")";
157 		rv = devices.insert (std::make_pair (cn, devname));
158 		cn = card_name + " [" + cnt + "]";
159 	} while (!rv.second && ++cnt <= '9');
160 
161 	if (!rv.second) {
162 		cn = card_name + " [" + devname + "] (";
163 		if (caps & SND_SEQ_PORT_CAP_READ) cn += "I";
164 		if (caps & SND_SEQ_PORT_CAP_WRITE) cn += "O";
165 		cn += ")";
166 		rv = devices.insert (std::make_pair (cn, devname));
167 		assert (rv.second == true);
168 	}
169 }
170 
171 void
get_alsa_rawmidi_device_names(std::map<std::string,std::string> & devices)172 ARDOUR::get_alsa_rawmidi_device_names (std::map<std::string, std::string>& devices)
173 {
174 	int cardnum = -1;
175 	snd_ctl_card_info_t *cinfo;
176 	snd_ctl_card_info_alloca (&cinfo);
177 	while (snd_card_next (&cardnum) >= 0 && cardnum >= 0) {
178 		snd_ctl_t *handle;
179 		std::string devname = "hw:";
180 		devname += PBD::to_string (cardnum);
181 		if (snd_ctl_open (&handle, devname.c_str (), 0) >= 0 && snd_ctl_card_info (handle, cinfo) >= 0) {
182 			int device = -1;
183 			while (snd_ctl_rawmidi_next_device (handle, &device) >= 0 && device >= 0) {
184 				snd_rawmidi_info_t *info;
185 				snd_rawmidi_info_alloca (&info);
186 				snd_rawmidi_info_set_device (info, device);
187 
188 				int subs_in, subs_out;
189 
190 				snd_rawmidi_info_set_stream (info, SND_RAWMIDI_STREAM_INPUT);
191 				if (snd_ctl_rawmidi_info (handle, info) >= 0) {
192 					subs_in = snd_rawmidi_info_get_subdevices_count (info);
193 				} else {
194 					subs_in = 0;
195 				}
196 
197 				snd_rawmidi_info_set_stream (info, SND_RAWMIDI_STREAM_OUTPUT);
198 				if (snd_ctl_rawmidi_info (handle, info) >= 0) {
199 					subs_out = snd_rawmidi_info_get_subdevices_count (info);
200 				} else {
201 					subs_out = 0;
202 				}
203 
204 				const int subs = subs_in > subs_out ? subs_in : subs_out;
205 				if (!subs) {
206 					continue;
207 				}
208 
209 				for (int sub = 0; sub < subs; ++sub) {
210 					snd_rawmidi_info_set_stream (info, sub < subs_in ?
211 							SND_RAWMIDI_STREAM_INPUT :
212 							SND_RAWMIDI_STREAM_OUTPUT);
213 
214 					snd_rawmidi_info_set_subdevice (info, sub);
215 					if (snd_ctl_rawmidi_info (handle, info) < 0) {
216 						continue;
217 					}
218 
219 					const char *sub_name = snd_rawmidi_info_get_subdevice_name (info);
220 					if (sub == 0 && sub_name[0] == '\0') {
221 						devname = "hw:";
222 						devname += snd_ctl_card_info_get_id (cinfo);
223 						devname += ",";
224 						devname += PBD::to_string (device);
225 
226 						std::string card_name = snd_rawmidi_info_get_name (info);
227 
228 						int caps = 0;
229 						if (sub < subs_in) caps |= SND_SEQ_PORT_CAP_READ;
230 						if (sub < subs_out) caps |= SND_SEQ_PORT_CAP_WRITE;
231 
232 						insert_unique_device_name (devices, card_name, devname, caps);
233 						break;
234 					} else {
235 						devname = "hw:";
236 						devname += snd_ctl_card_info_get_id (cinfo);
237 						devname += ",";
238 						devname += PBD::to_string (device);
239 						devname += ",";
240 						devname += PBD::to_string (sub);
241 
242 						int caps = 0;
243 						if (sub < subs_in) caps |= SND_SEQ_PORT_CAP_READ;
244 						if (sub < subs_out) caps |= SND_SEQ_PORT_CAP_WRITE;
245 						insert_unique_device_name (devices, sub_name, devname, caps);
246 					}
247 				}
248 			}
249 			snd_ctl_close (handle);
250 		}
251 	}
252 }
253 
254 void
get_alsa_sequencer_names(std::map<std::string,std::string> & devices)255 ARDOUR::get_alsa_sequencer_names (std::map<std::string, std::string>& devices)
256 {
257 	snd_seq_t *seq= NULL;
258 	snd_seq_client_info_t *cinfo;
259 	snd_seq_port_info_t *pinfo;
260 
261 	snd_seq_client_info_alloca (&cinfo);
262 	snd_seq_port_info_alloca (&pinfo);
263 
264 	if (snd_seq_open (&seq, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
265 		return;
266 	}
267 
268 	snd_seq_client_info_set_client(cinfo, -1);
269 	while (snd_seq_query_next_client (seq, cinfo) >= 0) {
270 		int client = snd_seq_client_info_get_client (cinfo);
271 		if (client == SND_SEQ_CLIENT_SYSTEM) {
272 			continue;
273 		}
274 		if (!strcmp (snd_seq_client_info_get_name(cinfo), "Midi Through")) {
275 			continue;
276 		}
277 		snd_seq_port_info_set_client (pinfo, client);
278 		snd_seq_port_info_set_port (pinfo, -1);
279 
280 		while (snd_seq_query_next_port (seq, pinfo) >= 0) {
281 			int caps = snd_seq_port_info_get_capability(pinfo);
282 			if (0 == (caps & (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_WRITE))) {
283 				continue;
284 			}
285 			if (caps & SND_SEQ_PORT_CAP_NO_EXPORT) {
286 				continue;
287 			}
288 			std::string card_name;
289 			card_name = snd_seq_port_info_get_name (pinfo);
290 
291 			std::string devname;
292 			devname = PBD::to_string(snd_seq_port_info_get_client (pinfo));
293 			devname += ":";
294 			devname += PBD::to_string(snd_seq_port_info_get_port (pinfo));
295 			insert_unique_device_name (devices, card_name, devname, caps);
296 		}
297 	}
298 	snd_seq_close (seq);
299 }
300 
301 int
card_to_num(const char * device_name)302 ARDOUR::card_to_num(const char* device_name)
303 {
304 	char* ctl_name;
305 	const char * comma;
306 	snd_ctl_t* ctl_handle;
307 	int i = -1;
308 
309 	if (strncasecmp(device_name, "plughw:", 7) == 0) {
310 		device_name += 4;
311 	}
312 	if (!(comma = strchr(device_name, ','))) {
313 		ctl_name = strdup(device_name);
314 	} else {
315 		ctl_name = strndup(device_name, comma - device_name);
316 	}
317 
318 	if (snd_ctl_open (&ctl_handle, ctl_name, 0) >= 0) {
319 		snd_ctl_card_info_t *card_info;
320 		snd_ctl_card_info_alloca (&card_info);
321 		if (snd_ctl_card_info(ctl_handle, card_info) >= 0) {
322 			i = snd_ctl_card_info_get_card(card_info);
323 		}
324 		snd_ctl_close(ctl_handle);
325 	}
326 	free(ctl_name);
327 	return i;
328 }
329