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