1 // ----------------------------------------------------------------------------
2 // soundconf.cxx
3 //
4 // Copyright (C) 2008-2010, Stelios Bounanos, M0GLD
5 // Copyright (C) 2014       David Freese, W1HKJ
6 // Copyright (C) 2015       Robert Stiles, KK5VD
7 //
8 // This file is part of fldigi.
9 //
10 // Fldigi is free software: you can redistribute it and/or modify
11 // it under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Fldigi is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with fldigi.  If not, see <http://www.gnu.org/licenses/>.
22 // ----------------------------------------------------------------------------
23 
24 #include <config.h>
25 
26 #if USE_PORTAUDIO
27 #  include <map>
28 #  include <list>
29 #endif
30 
31 #include <cstdlib>
32 #include <cstring>
33 #include <string>
34 #if USE_OSS
35 #  include <glob.h>
36 #endif
37 
38 #include "soundconf.h"
39 #include "sound.h"
40 #include "main.h"
41 #include "configuration.h"
42 #include "confdialog.h"
43 #include "debug.h"
44 #include "util.h"
45 
46 LOG_FILE_SOURCE(debug::LOG_AUDIO);
47 
48 using namespace std;
49 
50 double std_sample_rates[] = { 8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0,
51 	32000.0, 44100.0, 48000.0, 88200.0, 96000.0, 192000.0, -1.0 };
52 
init_oss(void)53 static void init_oss(void)
54 {
55 #if USE_OSS
56 #ifdef __FreeBSD__
57 	char *last = NULL;
58 	char *curr = NULL;
59 	char *p;
60 #endif
61 	glob_t gbuf;
62 	glob("/dev/dsp*", 0, NULL, &gbuf);
63 	if (gbuf.gl_pathc == 0) {
64 		AudioOSS->deactivate();
65 		btnAudioIO[SND_IDX_OSS]->deactivate();
66 		menuOSSDev->deactivate();
67 		return;
68 	}
69 	for (size_t i = 0; i < gbuf.gl_pathc; i++) {
70 #ifdef __FreeBSD__
71 		if (curr)
72 			free(curr);
73 		curr = strdup(gbuf.gl_pathv[i]);
74 		p = strrchr(curr, '.');
75 		if (p)
76 			*p = '\0';
77 		if (last != NULL) {
78 			if (strcmp(last, curr) == 0)
79 				continue;
80 		}
81 		menuOSSDev->add(curr);
82 		if (last)
83 			free(last);
84 		last = curr;
85 		curr = NULL;
86 #else
87 		menuOSSDev->add(gbuf.gl_pathv[i]);
88 #endif
89 	}
90 #ifdef __FreeBSD__
91 	if (last)
92 		free(last);
93 	if (curr)
94 		free(curr);
95 #endif
96 	if (progdefaults.OSSdevice.length() == 0 && gbuf.gl_pathc)
97 		progdefaults.OSSdevice = gbuf.gl_pathv[0];
98 	menuOSSDev->value(progdefaults.OSSdevice.c_str());
99 	globfree(&gbuf);
100 	menuOSSDev->activate();
101 #endif // USE_OSS
102 }
103 
104 
105 #if USE_PORTAUDIO
106 
107 map<PaHostApiTypeId, unsigned> pa_api_prio;
108 
109 struct padev
110 {
111 public:
padevpadev112 	padev(const PaDeviceInfo* dev_, PaDeviceIndex idx_, PaHostApiTypeId api_)
113 	: dev(dev_), idx(idx_), api(api_) { }
114 
operator <padev115 	bool operator<(const padev& rhs) const
116 	{
117 		return pa_api_prio.find(api) != pa_api_prio.end() &&
118 		pa_api_prio.find(rhs.api) != pa_api_prio.end() &&
119 		pa_api_prio[api] < pa_api_prio[rhs.api];
120 	}
121 
122 	const PaDeviceInfo* dev;
123 	PaDeviceIndex idx;
124 	PaHostApiTypeId api;
125 };
126 
get_default_portaudio_device(int dir)127 static PaDeviceIndex get_default_portaudio_device(int dir)
128 {
129 #ifndef __linux__
130 	goto ret_def;
131 #else
132 	// Recent PortAudio snapshots prefer ALSA over OSS for the default device, but there are
133 	// still versions out there that try OSS first.  We check the default host api type and,
134 	// if it is not ALSA, return the ALSA default device instead.
135 	PaHostApiIndex api_idx;
136 	if ((api_idx = Pa_GetDefaultHostApi()) < 0)
137 		goto ret_def;
138 	const PaHostApiInfo* host_api;
139 	if ((host_api = Pa_GetHostApiInfo(api_idx)) == NULL || host_api->type == paALSA)
140 		goto ret_def;
141 
142 	LOG_DEBUG("Default host API is %s, trying default ALSA %s device instead",
143 			  host_api->name, (dir == 0 ? "input" : "output"));
144 	api_idx = Pa_GetHostApiCount();
145 	if (api_idx < 0)
146 		goto ret_def;
147 	for (PaHostApiIndex i = 0; i < api_idx; i++)
148 		if ((host_api = Pa_GetHostApiInfo(i)) && host_api->type == paALSA)
149 			return dir == 0 ? host_api->defaultInputDevice : host_api->defaultOutputDevice;
150 #endif // __linux__
151 
152 ret_def:
153 	return dir == 0 ? Pa_GetDefaultInputDevice() : Pa_GetDefaultOutputDevice();
154 }
155 
156 #include <cerrno>
157 
158 std::string str_pa_devices;
159 
init_portaudio(void)160 static void init_portaudio(void)
161 {
162 	try {
163 		SoundPort::initialize();
164 	}
165 	catch (const SndException& e) {
166 //		if (e.error() == ENODEV) // don't complain if there are no devices
167 //			return;
168 		str_pa_devices.assign("\nPortaudio devices init failure:");
169 		str_pa_devices.assign(e.what());
170 		AudioPort->deactivate();
171 		btnAudioIO[SND_IDX_PORT]->deactivate();
172 		if (progdefaults.btnAudioIOis == SND_IDX_PORT)
173 			progdefaults.btnAudioIOis = SND_IDX_NULL;
174 		return;
175 	}
176 
177 	pa_api_prio.clear();
178 #if defined(__APPLE__)
179 	pa_api_prio[paASIO] = 0;
180 	pa_api_prio[paCoreAudio] = 1;
181 #elif defined(__WOE32__)
182 	pa_api_prio[paASIO] = 0;
183 	pa_api_prio[paWASAPI] = 1;
184 	pa_api_prio[paMME] = 2;
185 	pa_api_prio[paDirectSound] = 3;
186 #else
187 	pa_api_prio[paALSA] = 0;
188 	pa_api_prio[paJACK] = 1;
189 	pa_api_prio[paOSS] = 2;
190 #endif
191 
192 	list<padev> devlist;
193 	int devnbr = 0;
194 	for (SoundPort::device_iterator idev = SoundPort::devices().begin();
195 		 idev != SoundPort::devices().end(); ++idev) {
196 		devlist.push_back( padev(*idev, idev - SoundPort::devices().begin(),
197 								 Pa_GetHostApiInfo((*idev)->hostApi)->type) );
198 		devnbr++;
199 	}
200 	devlist.sort();
201 
202 	str_pa_devices.assign("\nPortaudio devices:\n");
203 	PaHostApiTypeId first_api = devlist.begin()->api;
204 	for (list<padev>::const_iterator ilist = devlist.begin();
205 		 ilist != devlist.end(); ilist++) {
206 		string menu_item;
207 		string::size_type i = 0;
208 		if (ilist->api != first_api) { // add a submenu
209 			menu_item.append(Pa_GetHostApiInfo(ilist->dev->hostApi)->name).append(" devices/");
210 			i = menu_item.length();
211 		}
212 		menu_item.append(ilist->dev->name);
213 
214 		str_pa_devices.append(menu_item).append("\n");
215 
216 		// backslash-escape any slashes in the device name
217 		while ((i = menu_item.find('/', i)) != string::npos) {
218 			menu_item.insert(i, 1, '\\');
219 			i += 2;
220 		}
221 
222 		// add to menu
223 		if (ilist->dev->maxInputChannels > 0)
224 			menuPortInDev->add(menu_item.c_str(), 0, NULL,
225 							   reinterpret_cast<void *>(ilist->idx), 0);
226 
227 		if (ilist->dev->maxOutputChannels > 0) {
228 			menuPortOutDev->add(menu_item.c_str(), 0, NULL,
229 								reinterpret_cast<void *>(ilist->idx), 0);
230 			menuAlertsDev->add(menu_item.c_str(), 0, NULL,
231 								reinterpret_cast<void *>(ilist->idx), 0);
232 		}
233 	}
234 
235 	if (progdefaults.PortInDevice.length() == 0) {
236 		if (progdefaults.PAdevice.length() == 0) {
237 			PaDeviceIndex def = get_default_portaudio_device(0);
238 			if (def != paNoDevice) {
239 				progdefaults.PortInDevice = (*(SoundPort::devices().begin() + def))->name;
240 				progdefaults.PortInIndex = def;
241 			}
242 		}
243 		else
244 			progdefaults.PortInDevice = progdefaults.PAdevice;
245 	}
246 
247 	if (progdefaults.PortOutDevice.length() == 0) {
248 		if (progdefaults.PAdevice.length() == 0) {
249 			PaDeviceIndex def = get_default_portaudio_device(1);
250 			if (def != paNoDevice) {
251 				progdefaults.PortOutDevice = (*(SoundPort::devices().begin() + def))->name;
252 				progdefaults.PortOutIndex = def;
253 			}
254 		}
255 		else
256 			progdefaults.PortOutDevice = progdefaults.PAdevice;
257 	}
258 
259 	if (progdefaults.AlertDevice.length() == 0) {
260 		PaDeviceIndex def = get_default_portaudio_device(1);
261 		if (def != paNoDevice) {
262 			progdefaults.AlertDevice = (*(SoundPort::devices().begin() + def))->name;
263 			progdefaults.AlertIndex = def;
264 		}
265 	}
266 
267 	// select the correct menu items
268 	pa_set_dev(menuPortInDev,  progdefaults.PortInDevice,  progdefaults.PortInIndex);
269 	pa_set_dev(menuPortOutDev, progdefaults.PortOutDevice, progdefaults.PortOutIndex);
270 
271 	pa_set_dev(menuAlertsDev, progdefaults.AlertDevice, progdefaults.AlertIndex);
272 }
273 
pa_set_dev(Fl_Choice * loc_choice,std::string loc_dev_name,int loc_dev_index)274 int pa_set_dev(Fl_Choice *loc_choice, std::string loc_dev_name, int loc_dev_index)
275 {
276 	const Fl_Menu_Item *loc_menu = (Fl_Menu_Item *)0;
277 	int loc_size = 0;
278 	int loc_dev_found = PA_DEV_NOT_FOUND;
279 	int loc_idx = -1;
280 
281 	if(!loc_choice) return loc_dev_found;
282 
283 	loc_menu = loc_choice->menu();
284 	loc_size = loc_choice->size();
285 
286 	for (int loc_i = 0; loc_i < loc_size - 1; loc_i++, loc_menu++) {
287 		if (loc_menu->label() && loc_dev_name == loc_menu->label()) {
288 			loc_idx = loc_i;
289 			loc_dev_found = PA_DEV_FOUND;
290 			if (reinterpret_cast<intptr_t>(loc_menu->user_data()) == loc_dev_index ||
291 				loc_dev_found == -1) { // exact match, or index was never saved
292 				loc_dev_found = PA_EXACT_DEV_FOUND;
293 			}
294 		}
295 	}
296 
297 	if (loc_idx >= 0) {
298 		loc_choice->value(loc_idx);
299 		loc_choice->set_changed();
300 	}
301 
302 	return loc_dev_found;
303 }
304 
305 #else
init_portaudio(void)306 static void init_portaudio(void) { }
307 #endif // USE_PORTAUDIO
308 
build_srate_listbox(Fl_ListBox * lbox,const double * rates,size_t length,double defrate=-1.0)309 static void build_srate_listbox(Fl_ListBox* lbox, const double* rates, size_t length, double defrate = -1.0)
310 {
311 	lbox->clear();
312 	lbox->add("Auto");
313 	lbox->add("Native");
314 
315 	char s[16];
316 	for (size_t i = 0; i < length; i++) {
317 		if (defrate != rates[i])
318 			snprintf(s, sizeof(s), "%.0f", rates[i]);
319 		else
320 			snprintf(s, sizeof(s), "%.0f (native)", rates[i]);
321 		lbox->add(s);
322 	}
323 }
324 
325 int sample_rate_converters[FLDIGI_NUM_SRC] = {
326 	SRC_SINC_BEST_QUALITY,
327 	SRC_SINC_MEDIUM_QUALITY,
328 	SRC_SINC_FASTEST
329 #if !(defined(__ppc__) || defined(__powerpc__) || defined(__PPC__))
330 	, SRC_LINEAR
331 #endif
332 };
333 
sound_init_options(void)334 static void sound_init_options(void)
335 {
336 	build_srate_listbox(menuInSampleRate, std_sample_rates,
337 						sizeof(std_sample_rates)/sizeof(*std_sample_rates) - 1);
338 	build_srate_listbox(menuOutSampleRate, std_sample_rates,
339 						sizeof(std_sample_rates)/sizeof(*std_sample_rates) - 1);
340 
341 	for (int i = 0; i < FLDIGI_NUM_SRC; i++)
342 		menuSampleConverter->add(src_get_name(sample_rate_converters[i]));
343 	// Warn if we are using ZOH
344 	if (progdefaults.sample_converter == SRC_ZERO_ORDER_HOLD) {
345 		progdefaults.sample_converter = SRC_LINEAR;
346 		LOG_WARN("The Zero Order Hold sample rate converter should not be used! "
347 				 "Your setting has been changed to Linear.");
348 	}
349 #if defined(__ppc__) || defined(__powerpc__) || defined(__PPC__)
350 	// SRC_LINEAR may crash with 11025Hz modems. Change to SINC_FASTEST.
351 	if (progdefaults.sample_converter == SRC_LINEAR) {
352 		progdefaults.sample_converter = SRC_SINC_FASTEST;
353 		LOG_WARN("Linear sample rate converter may not work on this architecture. "
354 				 "Your setting has been changed to Fastest Sinc");
355 	}
356 #endif
357 	for (int i = 0; i < FLDIGI_NUM_SRC; i++) {
358 		if (sample_rate_converters[i] == progdefaults.sample_converter) {
359 			menuSampleConverter->index(i);
360 			menuSampleConverter->tooltip(src_get_description(progdefaults.sample_converter));
361 			break;
362 		}
363 	}
364 
365 	menuOSSDev->value(progdefaults.OSSdevice.c_str());
366 	inpPulseServer->value(progdefaults.PulseServer.c_str());
367 
368 	char sr[20];
369 	if (progdefaults.in_sample_rate == SAMPLE_RATE_UNSET &&
370 		(progdefaults.in_sample_rate = progdefaults.sample_rate) == SAMPLE_RATE_UNSET)
371 		progdefaults.in_sample_rate = SAMPLE_RATE_NATIVE;
372 	else if (progdefaults.in_sample_rate < SAMPLE_RATE_OTHER)
373 		menuInSampleRate->index(progdefaults.in_sample_rate);
374 	else {
375 		snprintf(sr, sizeof(sr), "%d", progdefaults.in_sample_rate);
376 		for (int i = SAMPLE_RATE_NATIVE + 1; i < menuInSampleRate->lsize(); i++) {
377 			menuInSampleRate->index(i);
378 			if (strstr(menuInSampleRate->value(), sr))
379 				break;
380 		}
381 	}
382 
383 	if (progdefaults.out_sample_rate == SAMPLE_RATE_UNSET &&
384 		(progdefaults.out_sample_rate = progdefaults.sample_rate) == SAMPLE_RATE_UNSET)
385 		progdefaults.out_sample_rate = SAMPLE_RATE_NATIVE;
386 	else if (progdefaults.out_sample_rate < SAMPLE_RATE_OTHER)
387 		menuOutSampleRate->index(progdefaults.out_sample_rate);
388 	else {
389 		snprintf(sr, sizeof(sr), "%d", progdefaults.out_sample_rate);
390 		for (int i = SAMPLE_RATE_NATIVE + 1; i < menuOutSampleRate->lsize(); i++) {
391 			menuOutSampleRate->index(i);
392 			if (strstr(menuOutSampleRate->value(), sr))
393 				break;
394 		}
395 	}
396 
397 	cntRxRateCorr->value(progdefaults.RX_corr);
398 	cntTxRateCorr->value(progdefaults.TX_corr);
399 	cntTxOffset->value(progdefaults.TxOffset);
400 }
401 
402 #if USE_PULSEAUDIO
403 #  include <pulse/context.h>
404 #  include <pulse/mainloop.h>
405 #  include <pulse/version.h>
406 
407 #  if PA_API_VERSION < 12
PA_CONTEXT_IS_GOOD(pa_context_state_t x)408 static inline int PA_CONTEXT_IS_GOOD(pa_context_state_t x) {
409 	return  x == PA_CONTEXT_CONNECTING || x == PA_CONTEXT_AUTHORIZING ||
410 	x == PA_CONTEXT_SETTING_NAME || x == PA_CONTEXT_READY;
411 }
412 #  endif
413 
probe_pulseaudio(void)414 static bool probe_pulseaudio(void)
415 {
416 	pa_mainloop* loop = pa_mainloop_new();
417 	if (!loop)
418 		return false;
419 
420 	bool ok = false;
421 	pa_context* context = pa_context_new(pa_mainloop_get_api(loop), PACKAGE_TARNAME);
422 	if (context && pa_context_connect(context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) >= 0) {
423 		pa_context_state_t state;
424 		do { // iterate main loop until the context connection fails or becomes ready
425 			if (!(ok = (pa_mainloop_iterate(loop, 1, NULL) >= 0 &&
426 						PA_CONTEXT_IS_GOOD(state = pa_context_get_state(context)))))
427 				break;
428 		} while (state != PA_CONTEXT_READY);
429 	}
430 
431 	if (context) {
432 		pa_context_disconnect(context);
433 		pa_context_unref(context);
434 	}
435 	pa_mainloop_free(loop);
436 
437 	return ok;
438 }
439 #else
probe_pulseaudio(void)440 static bool probe_pulseaudio(void) { return false; }
441 #endif // USE_PULSEAUDIO
442 
sound_init(void)443 void sound_init(void)
444 {
445 	init_oss();
446 
447 	init_portaudio();
448 
449 	// set the Sound Card configuration tab to the correct initial values
450 #if !USE_OSS
451 	AudioOSS->deactivate();
452 	btnAudioIO[SND_IDX_OSS]->deactivate();
453 #endif
454 #if !USE_PORTAUDIO
455 	AudioPort->deactivate();
456 	btnAudioIO[SND_IDX_PORT]->deactivate();
457 #endif
458 #if !USE_PULSEAUDIO
459 	AudioPulse->deactivate();
460 	btnAudioIO[SND_IDX_PULSE]->deactivate();
461 	inpPulseServer->deactivate();
462 #endif
463 	if (progdefaults.btnAudioIOis == SND_IDX_UNKNOWN ||
464 		!btnAudioIO[progdefaults.btnAudioIOis]->active()) { // or saved sound api now disabled
465 		int io[4] = { SND_IDX_PORT, SND_IDX_PULSE, SND_IDX_OSS, SND_IDX_NULL };
466 		if (probe_pulseaudio()) { // prefer pulseaudio
467 			io[0] = SND_IDX_PULSE;
468 			io[1] = SND_IDX_PORT;
469 		}
470 		for (size_t i = 0; i < sizeof(io)/sizeof(*io); i++) {
471 			if (btnAudioIO[io[i]]->active()) {
472 				progdefaults.btnAudioIOis = io[i];
473 				break;
474 			}
475 		}
476 	}
477 
478 	sound_init_options();
479 
480 	sound_update(progdefaults.btnAudioIOis);
481 
482 }
483 
sound_close(void)484 void sound_close(void)
485 {
486 #if USE_PORTAUDIO
487 	SoundPort::terminate();
488 #endif
489 }
490 
sound_update(unsigned idx)491 void sound_update(unsigned idx)
492 {
493 	// radio button
494 	for (size_t i = 0; i < sizeof(btnAudioIO)/sizeof(*btnAudioIO); i++)
495 		btnAudioIO[i]->value(i == idx);
496 
497 	// devices
498 	menuOSSDev->deactivate();
499 	menuPortInDev->deactivate();
500 	menuPortOutDev->deactivate();
501 
502 	// settings
503 	menuInSampleRate->deactivate();
504 	menuOutSampleRate->deactivate();
505 
506 	progdefaults.btnAudioIOis = idx;
507 	switch (idx) {
508 #if USE_OSS
509 		case SND_IDX_OSS:
510 			scDevice[0] = scDevice[1] = menuOSSDev->value();
511 			menuOSSDev->activate();
512 			break;
513 #endif
514 
515 #if USE_PORTAUDIO
516 		case SND_IDX_PORT:
517 			menuPortInDev->activate();
518 			menuPortOutDev->activate();
519 			if (menuPortInDev->text())
520 				scDevice[0] = menuPortInDev->text();
521 			if (menuPortOutDev->text())
522 				scDevice[1] = menuPortOutDev->text();
523 
524 		{
525 			Fl_ListBox* listbox[2] = { menuInSampleRate, menuOutSampleRate };
526 			for (size_t i = 0; i < 2; i++) {
527 				char* label = strdup(listbox[i]->value());
528 				const vector<double>& srates = SoundPort::get_supported_rates(scDevice[i], i);
529 
530 				switch (srates.size()) {
531 					case 0: // startup; no devices initialised yet
532 						build_srate_listbox(listbox[i], std_sample_rates,
533 											sizeof(std_sample_rates)/sizeof(*std_sample_rates) - 1);
534 						break;
535 					case 1: // default sample rate only, build menu with all std rates
536 						build_srate_listbox(listbox[i], std_sample_rates,
537 											sizeof(std_sample_rates)/sizeof(*std_sample_rates) - 1, srates[0]);
538 
539 						break;
540 					default: // first element is default sample rate, build menu with rest
541 						build_srate_listbox(listbox[i], &srates[0] + 1, srates.size() - 1, srates[0]);
542 						break;
543 				}
544 
545 				for (int j = 0; j < listbox[i]->lsize(); j++) {
546 					listbox[i]->index(j);
547 					if (strstr(listbox[i]->value(), label))
548 						break;
549 				}
550 				free(label);
551 
552 				listbox[i]->activate();
553 			}
554 		}
555 			break;
556 #endif
557 
558 #if USE_PULSEAUDIO
559 		case SND_IDX_PULSE:
560 			scDevice[0] = scDevice[1] = inpPulseServer->value();
561 			break;
562 #endif
563 
564 		case SND_IDX_NULL:
565 			scDevice[0] = scDevice[1] = "";
566 			break;
567 	};
568 }
569 
570