first part of NSD changes - if no interface has been identified previously, put the...
[ardour.git] / gtk2_ardour / engine_dialog.cc
1 #include <vector>
2 #include <cmath>
3 #include <fstream>
4 #include <map>
5
6 #include <glibmm.h>
7 #include <gtkmm/messagedialog.h>
8 #include <pbd/xml++.h>
9
10 #ifdef __APPLE__
11 #include <CoreAudio/CoreAudio.h>
12 #include <CoreFoundation/CFString.h>
13 #include <sys/param.h>
14 #include <mach-o/dyld.h>
15 #else
16 #include <alsa/asoundlib.h>
17 #endif
18
19 #include <ardour/profile.h>
20 #include <jack/jack.h>
21
22 #include <gtkmm/stock.h>
23 #include <gtkmm2ext/utils.h>
24
25 #include <pbd/convert.h>
26 #include <pbd/error.h>
27 #include <pbd/pathscanner.h>
28
29 #ifdef __APPLE
30 #include <CFBundle.h>
31 #endif
32
33 #include "engine_dialog.h"
34 #include "i18n.h"
35
36 using namespace std;
37 using namespace Gtk;
38 using namespace Gtkmm2ext;
39 using namespace PBD;
40 using namespace Glib;
41
42 EngineControl::EngineControl ()
43         : periods_adjustment (2, 2, 16, 1, 2),
44           periods_spinner (periods_adjustment),
45           priority_adjustment (60, 10, 90, 1, 10),
46           priority_spinner (priority_adjustment),
47           ports_adjustment (128, 8, 1024, 1, 16),
48           ports_spinner (ports_adjustment),
49           realtime_button (_("Realtime")),
50           no_memory_lock_button (_("Do not lock memory")),
51           unlock_memory_button (_("Unlock memory")),
52           soft_mode_button (_("No zombies")),
53           monitor_button (_("Provide monitor ports")),
54           force16bit_button (_("Force 16 bit")),
55           hw_monitor_button (_("H/W monitoring")),
56           hw_meter_button (_("H/W metering")),
57           verbose_output_button (_("Verbose output")),
58           start_button (_("Start")),
59           stop_button (_("Stop")),
60 #ifdef __APPLE__
61           basic_packer (5, 2),
62           options_packer (4, 2),
63           device_packer (4, 2)
64 #else
65           basic_packer (8, 2),
66           options_packer (14, 2),
67           device_packer (6, 2)
68 #endif    
69 {
70         using namespace Notebook_Helpers;
71         Label* label;
72         vector<string> strings;
73         int row = 0;
74
75         _used = false;
76         _interface_chosen = false;
77
78         strings.push_back (_("8000Hz"));
79         strings.push_back (_("22050Hz"));
80         strings.push_back (_("44100Hz"));
81         strings.push_back (_("48000Hz"));
82         strings.push_back (_("88200Hz"));
83         strings.push_back (_("96000Hz"));
84         strings.push_back (_("192000Hz"));
85         set_popdown_strings (sample_rate_combo, strings);
86         sample_rate_combo.set_active_text ("48000Hz");
87
88         strings.clear ();
89         strings.push_back ("32");
90         strings.push_back ("64");
91         strings.push_back ("128");
92         strings.push_back ("256");
93         strings.push_back ("512");
94         strings.push_back ("1024");
95         strings.push_back ("2048");
96         strings.push_back ("4096");
97         strings.push_back ("8192");
98         set_popdown_strings (period_size_combo, strings);
99         period_size_combo.set_active_text ("1024");
100
101         strings.clear ();
102         strings.push_back (_("None"));
103         strings.push_back (_("Triangular"));
104         strings.push_back (_("Rectangular"));
105         strings.push_back (_("Shaped"));
106         set_popdown_strings (dither_mode_combo, strings);
107         dither_mode_combo.set_active_text (_("None"));
108
109         /* basic parameters */
110
111         basic_packer.set_spacings (6);
112
113         strings.clear ();
114 #ifdef __APPLE__
115         strings.push_back (X_("CoreAudio"));
116 #else
117         strings.push_back (X_("ALSA"));
118         strings.push_back (X_("OSS"));
119         strings.push_back (X_("FFADO"));
120 #endif
121         strings.push_back (X_("NetJACK"));
122         strings.push_back (X_("Dummy"));
123         set_popdown_strings (driver_combo, strings);
124         driver_combo.set_active_text (strings.front());
125
126         driver_combo.signal_changed().connect (mem_fun (*this, &EngineControl::driver_changed));
127         driver_changed ();
128
129         strings.clear ();
130         strings.push_back (_("Playback/Recording on 1 Device"));
131         strings.push_back (_("Playback/Recording on 2 Devices"));
132         strings.push_back (_("Playback only"));
133         strings.push_back (_("Recording only"));
134         set_popdown_strings (audio_mode_combo, strings);
135         audio_mode_combo.set_active_text (strings.front());
136
137         audio_mode_combo.signal_changed().connect (mem_fun (*this, &EngineControl::audio_mode_changed));
138         audio_mode_changed ();
139
140         row = 0;
141
142         label = manage (new Label (_("Driver")));
143         basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
144         basic_packer.attach (driver_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
145         row++;
146
147         label = manage (new Label (_("Interface")));
148         basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
149         basic_packer.attach (interface_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
150         row++;
151
152         label = manage (new Label (_("Sample Rate")));
153         basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
154         basic_packer.attach (sample_rate_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
155         row++;
156
157         label = manage (new Label (_("Buffer size")));
158         basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
159         basic_packer.attach (period_size_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
160         row++;
161
162 #ifndef __APPLE__
163         label = manage (new Label (_("Number of buffers")));
164         basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
165         basic_packer.attach (periods_spinner, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
166         periods_spinner.set_value (2);
167         row++;
168 #endif
169
170         label = manage (new Label (_("Approximate latency")));
171         label->set_alignment (0.0, 0.5);
172         basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
173         basic_packer.attach (latency_label, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
174         row++;
175
176         sample_rate_combo.signal_changed().connect (mem_fun (*this, &EngineControl::redisplay_latency));
177         periods_adjustment.signal_value_changed().connect (mem_fun (*this, &EngineControl::redisplay_latency));
178         period_size_combo.signal_changed().connect (mem_fun (*this, &EngineControl::redisplay_latency));
179         redisplay_latency();
180         row++;
181         /* no audio mode with CoreAudio, its duplex or nuthin' */
182
183 #ifndef __APPLE__
184         label = manage (new Label (_("Audio Mode")));
185         basic_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
186         basic_packer.attach (audio_mode_combo, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
187         row++;
188 #endif
189
190         interface_combo.set_size_request (125, -1);
191         input_device_combo.set_size_request (125, -1);
192         output_device_combo.set_size_request (125, -1);
193
194         /*
195
196         if (engine_running()) {
197                 start_button.set_sensitive (false);
198         } else {
199                 stop_button.set_sensitive (false);
200         }
201
202         start_button.signal_clicked().connect (mem_fun (*this, &EngineControl::start_engine));
203         stop_button.signal_clicked().connect (mem_fun (*this, &EngineControl::start_engine));
204         */
205
206         button_box.pack_start (start_button, false, false);
207         button_box.pack_start (stop_button, false, false);
208
209         // basic_packer.attach (button_box, 0, 2, 8, 9, FILL|EXPAND, (AttachOptions) 0);
210
211         /* options */
212
213         options_packer.set_spacings (6);
214         row = 0;
215
216         options_packer.attach (realtime_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
217         ++row;
218
219         realtime_button.signal_toggled().connect (mem_fun (*this, &EngineControl::realtime_changed));
220         realtime_changed ();
221
222 #ifndef __APPLE__
223         label = manage (new Label (_("Realtime Priority")));
224         label->set_alignment (1.0, 0.5);
225         options_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
226         options_packer.attach (priority_spinner, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
227         ++row;
228         priority_spinner.set_value (60);
229
230         options_packer.attach (no_memory_lock_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
231         ++row;
232         options_packer.attach (unlock_memory_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
233         ++row;
234         options_packer.attach (soft_mode_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
235         ++row;
236         options_packer.attach (monitor_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
237         ++row;
238         options_packer.attach (force16bit_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
239         ++row;
240         options_packer.attach (hw_monitor_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
241         ++row;
242         options_packer.attach (hw_meter_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
243         ++row;
244         options_packer.attach (verbose_output_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
245         ++row;
246 #else 
247         options_packer.attach (verbose_output_button, 1, 2, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
248         ++row;
249 #endif
250
251         strings.clear ();
252         strings.push_back (_("Ignore"));
253         strings.push_back ("500 msec");
254         strings.push_back ("1 sec");
255         strings.push_back ("2 sec");
256         strings.push_back ("10 sec");
257         set_popdown_strings (timeout_combo, strings);
258         timeout_combo.set_active_text (strings.front ());
259
260         label = manage (new Label (_("Client timeout")));
261         label->set_alignment (1.0, 0.5);
262         options_packer.attach (timeout_combo, 1, 2, row, row + 1, FILL|EXPAND, AttachOptions(0));
263         options_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
264         ++row;
265
266         label = manage (new Label (_("Number of ports")));
267         label->set_alignment (1.0, 0.5);
268         options_packer.attach (ports_spinner, 1, 2, row, row + 1, FILL|EXPAND, AttachOptions(0));
269         options_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
270         ++row;
271
272 #ifndef __APPLE__
273         label = manage (new Label (_("Dither")));       
274         label->set_alignment (1.0, 0.5);
275         options_packer.attach (dither_mode_combo, 1, 2, row, row + 1, FILL|EXPAND, AttachOptions(0));
276         options_packer.attach (*label, 0, 1, row, row + 1, FILL|EXPAND, (AttachOptions) 0);
277         ++row;
278 #endif
279
280         /* defer server stuff till later */
281         server_row = row++;
282
283         /* device settings */
284
285         device_packer.set_spacings (6);
286         row = 0;
287
288 #ifndef __APPLE__
289         label = manage (new Label (_("Input device")));
290         label->set_alignment (1.0, 0.5);
291         device_packer.attach (*label, 0, 1, row, row+1, FILL|EXPAND, (AttachOptions) 0);
292         device_packer.attach (input_device_combo, 1, 2, row, row+1, FILL|EXPAND, (AttachOptions) 0);
293         ++row;
294         label = manage (new Label (_("Output device")));
295         label->set_alignment (1.0, 0.5);
296         device_packer.attach (*label, 0, 1, row, row+1, FILL|EXPAND, (AttachOptions) 0);
297         device_packer.attach (output_device_combo, 1, 2, row, row+1, FILL|EXPAND, (AttachOptions) 0);   
298         ++row;
299 #endif
300         label = manage (new Label (_("Input channels")));
301         label->set_alignment (1.0, 0.5);
302         device_packer.attach (*label, 0, 1, row, row+1, FILL|EXPAND, (AttachOptions) 0);
303         device_packer.attach (input_channels, 1, 2, row, row+1, FILL|EXPAND, (AttachOptions) 0);
304         ++row;
305         label = manage (new Label (_("Output channels")));
306         label->set_alignment (1.0, 0.5);
307         device_packer.attach (*label, 0, 1, row, row+1, FILL|EXPAND, (AttachOptions) 0);
308         device_packer.attach (output_channels, 1, 2, row, row+1, FILL|EXPAND, (AttachOptions) 0);
309         ++row;
310         label = manage (new Label (_("Hardware input latency (samples)")));
311         label->set_alignment (1.0, 0.5);
312         device_packer.attach (*label, 0, 1, row, row+1, FILL|EXPAND, (AttachOptions) 0);
313         device_packer.attach (input_latency, 1, 2, row, row+1, FILL|EXPAND, (AttachOptions) 0);
314         ++row;
315         label = manage (new Label (_("Hardware output latency (samples)")));
316         label->set_alignment (1.0, 0.5);
317         device_packer.attach (*label, 0, 1, row, row+1, FILL|EXPAND, (AttachOptions) 0);
318         device_packer.attach (output_latency, 1, 2, row, row+1, FILL|EXPAND, (AttachOptions) 0);
319         ++row;
320
321         basic_hbox.pack_start (basic_packer, false, false);
322         options_hbox.pack_start (options_packer, false, false);
323
324         device_packer.set_border_width (12);
325         options_packer.set_border_width (12);
326         basic_packer.set_border_width (12);
327
328         notebook.pages().push_back (TabElem (basic_hbox, _("Device")));
329         notebook.pages().push_back (TabElem (options_hbox, _("Options")));
330         notebook.pages().push_back (TabElem (device_packer, _("Advanced")));
331         notebook.set_border_width (12);
332
333         set_border_width (12);
334         pack_start (notebook);
335 }
336
337 EngineControl::~EngineControl ()
338 {
339
340 }
341
342 void
343 EngineControl::discover_servers ()
344 {
345         find_jack_servers (server_strings);
346
347         if (server_strings.empty()) {
348                 fatal << _("No JACK server found anywhere on this system. Please install JACK and restart") << endmsg;
349                 /*NOTREACHED*/
350         }
351         
352         set_popdown_strings (serverpath_combo, server_strings);
353         serverpath_combo.set_active_text (server_strings.front());
354
355         if (server_strings.size() > 1) {
356                 Gtk::Label* label = manage (new Label (_("Server:")));
357                 options_packer.attach (*label, 0, 1, server_row, server_row + 1, FILL|EXPAND, (AttachOptions) 0);
358                 label->set_alignment (0.0, 0.5);
359                 options_packer.attach (serverpath_combo, 1, 2, server_row, server_row + 1, FILL|EXPAND, (AttachOptions) 0);
360         }
361 }
362
363 void
364 EngineControl::build_command_line (vector<string>& cmd)
365 {
366         string str;
367         string driver;
368         bool using_oss = false;
369         bool using_alsa = false;
370         bool using_coreaudio = false;
371         bool using_netjack = false;
372         bool using_ffado = false;
373         bool using_dummy = false;
374
375         /* first, path to jackd */
376
377         cmd.push_back (serverpath_combo.get_active_text ());
378         
379         /* now jackd arguments */
380
381         str = timeout_combo.get_active_text ();
382         if (str != _("Ignore")) {
383                 double secs;
384                 uint32_t msecs;
385                 secs = atof (str);
386                 msecs = (uint32_t) floor (secs * 1000.0);
387                 cmd.push_back ("-t");
388                 cmd.push_back (to_string (msecs, std::dec));
389         }
390
391         if (no_memory_lock_button.get_active()) {
392                 cmd.push_back ("-m"); /* no munlock */
393         }
394         
395         cmd.push_back ("-p"); /* port max */
396         cmd.push_back (to_string ((uint32_t) floor (ports_spinner.get_value()), std::dec));
397
398         if (realtime_button.get_active()) {
399                 cmd.push_back ("-R");
400                 cmd.push_back ("-P");
401                 cmd.push_back (to_string ((uint32_t) floor (priority_spinner.get_value()), std::dec));
402         }
403
404         if (unlock_memory_button.get_active()) {
405                 cmd.push_back ("-u");
406         }
407
408         if (verbose_output_button.get_active()) {
409                 cmd.push_back ("-v");
410         }
411         
412         /* now add fixed arguments (not user-selectable) */
413
414         cmd.push_back ("-T"); // temporary */
415
416         /* next the driver */
417
418         cmd.push_back ("-d");
419
420         driver = driver_combo.get_active_text ();
421         if (driver == X_("ALSA")) {
422                 using_alsa = true;
423                 cmd.push_back ("alsa");
424         } else if (driver == X_("OSS")) {
425                 using_oss = true;
426                 cmd.push_back ("oss");
427         } else if (driver == X_("CoreAudio")) {
428                 using_coreaudio = true;
429                 cmd.push_back ("coreaudio");
430         } else if (driver == X_("NetJACK")) {
431                 using_netjack = true;
432                 cmd.push_back ("netjack");
433         } else if (driver == X_("FFADO")) {
434                 using_ffado = true;
435
436                 /* do this until FFADO becomes the standard */
437
438                 char* hack = getenv ("ARDOUR_FIREWIRE_DRIVER_NAME");
439
440                 if (hack) {
441                         cmd.push_back (hack);
442                 } else {
443                         cmd.push_back ("freebob");
444                 }
445
446         } else if ( driver == X_("Dummy")) {
447                 using_dummy = true;
448                 cmd.push_back ("dummy");
449         }
450
451         /* driver arguments */
452
453         if (!using_coreaudio) {
454                 str = audio_mode_combo.get_active_text();
455                 
456                 if (str == _("Playback/Recording on 1 Device")) {
457                         
458                         /* relax */
459                         
460                 } else if (str == _("Playback/Recording on 2 Devices")) {
461                         
462                         string input_device = get_device_name (driver, input_device_combo.get_active_text());
463                         string output_device = get_device_name (driver, output_device_combo.get_active_text());
464
465                         if (input_device.empty() || output_device.empty()) {
466                                 cmd.clear ();
467                                 return;
468                         }
469
470                         cmd.push_back ("-C");
471                         cmd.push_back (input_device);
472                         cmd.push_back ("-P");
473                         cmd.push_back (output_device);
474
475                 } else if (str == _("Playback only")) {
476                         cmd.push_back ("-P");
477                 } else if (str == _("Recording only")) {
478                         cmd.push_back ("-C");
479                 }
480
481                 if (! using_dummy ) {
482                         cmd.push_back ("-n");
483                         cmd.push_back (to_string ((uint32_t) floor (periods_spinner.get_value()), std::dec));
484                 }
485         }
486
487         cmd.push_back ("-r");
488         cmd.push_back (to_string (get_rate(), std::dec));
489         
490         cmd.push_back ("-p");
491         cmd.push_back (period_size_combo.get_active_text());
492
493         if (using_alsa) {
494                 
495                 if (audio_mode_combo.get_active_text() != _("Playback/Recording on 2 Devices")) {
496
497                         string device = get_device_name (driver, interface_combo.get_active_text());
498                         if (device.empty()) {
499                                 cmd.clear ();
500                                 return;
501                         }
502
503                         cmd.push_back ("-d");
504                         cmd.push_back (device);
505                         _interface_chosen = true;
506                 } 
507
508                 if (hw_meter_button.get_active()) {
509                         cmd.push_back ("-M");
510                 }
511                 
512                 if (hw_monitor_button.get_active()) {
513                         cmd.push_back ("-H");
514                 }
515
516                 str = dither_mode_combo.get_active_text();
517
518                 if (str == _("None")) {
519                 } else if (str == _("Triangular")) {
520                         cmd.push_back ("-z triangular");
521                 } else if (str == _("Rectangular")) {
522                         cmd.push_back ("-z rectangular");
523                 } else if (str == _("Shaped")) {
524                         cmd.push_back ("-z shaped");
525                 }
526
527                 if (force16bit_button.get_active()) {
528                         cmd.push_back ("-S");
529                 }
530                 
531                 if (soft_mode_button.get_active()) {
532                         cmd.push_back ("-s");
533                 }
534
535         } else if (using_coreaudio) {
536
537 #ifdef __APPLE__
538                 // note: older versions of the CoreAudio JACK backend use -n instead of -d here
539                 
540                 string device = get_device_name (driver, interface_combo.get_active_text());
541                 if (device.empty()) {
542                         cmd.clear ();
543                         return;
544                 }
545
546                 cmd.push_back ("-d");
547                 cmd.push_back (device);
548 #endif
549
550         } else if (using_oss) {
551
552         } else if (using_netjack) {
553
554         }
555 }
556
557 bool
558 EngineControl::engine_running ()
559 {
560         jack_status_t status;
561         jack_client_t* c = jack_client_open ("ardourprobe", JackNoStartServer, &status);
562
563         if (status == 0) {
564                 jack_client_close (c);
565                 return true;
566         }
567         return false;
568 }
569
570 int
571 EngineControl::setup_engine ()
572 {
573         vector<string> args;
574         std::string cwd = "/tmp";
575
576         build_command_line (args);
577         
578         if (args.empty()) {
579                 return 1; // try again
580         }
581
582         Glib::ustring jackdrc_path = Glib::get_home_dir();
583         jackdrc_path += "/.jackdrc";
584
585         ofstream jackdrc (jackdrc_path.c_str());
586         if (!jackdrc) {
587                 error << string_compose (_("cannot open JACK rc file %1 to store parameters"), jackdrc_path) << endmsg;
588                 return -1;
589         }
590         cerr << "JACK COMMAND: ";
591         for (vector<string>::iterator i = args.begin(); i != args.end(); ++i) {
592                 cerr << (*i) << ' ';
593                 jackdrc << (*i) << ' ';
594         }
595         cerr << endl;
596         jackdrc << endl;
597         jackdrc.close ();
598
599         _used = true;
600
601         return 0;
602 }
603
604 void
605 EngineControl::realtime_changed ()
606 {
607 #ifndef __APPLE__
608         priority_spinner.set_sensitive (realtime_button.get_active());
609 #endif
610 }
611
612 void
613 EngineControl::enumerate_devices (const string& driver)
614 {
615         /* note: case matters for the map keys */
616
617         if (driver == "CoreAudio") {
618 #ifdef __APPLE__                
619                 devices[driver] = enumerate_coreaudio_devices ();
620 #endif
621
622 #ifndef __APPLE__
623         } else if (driver == "ALSA") {
624                 devices[driver] = enumerate_alsa_devices ();
625         } else if (driver == "FFADO") {
626                 devices[driver] = enumerate_ffado_devices ();
627         } else if (driver == "OSS") {
628                 devices[driver] = enumerate_oss_devices ();
629         } else if (driver == "Dummy") {
630                 devices[driver] = enumerate_dummy_devices ();
631         } else if (driver == "NetJACK") {
632                 devices[driver] = enumerate_netjack_devices ();
633         }
634 #else
635         }
636 #endif
637 }
638
639 #ifdef __APPLE__
640 static OSStatus 
641 getDeviceUIDFromID( AudioDeviceID id, char *name, size_t nsize)
642 {
643         UInt32 size = sizeof(CFStringRef);
644         CFStringRef UI;
645         OSStatus res = AudioDeviceGetProperty(id, 0, false,
646                 kAudioDevicePropertyDeviceUID, &size, &UI);
647         if (res == noErr) 
648                 CFStringGetCString(UI,name,nsize,CFStringGetSystemEncoding());
649         CFRelease(UI);
650         return res;
651 }
652
653 vector<string>
654 EngineControl::enumerate_coreaudio_devices ()
655 {
656         vector<string> devs;
657         
658         // Find out how many Core Audio devices are there, if any...
659         // (code snippet gently "borrowed" from St?hane Letz jackdmp;)
660         OSStatus err;
661         Boolean isWritable;
662         size_t outSize = sizeof(isWritable);
663
664         backend_devs.clear ();
665
666         err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
667                                            &outSize, &isWritable);
668         if (err == noErr) {
669                 // Calculate the number of device available...
670                 int numCoreDevices = outSize / sizeof(AudioDeviceID);
671                 // Make space for the devices we are about to get...
672                 AudioDeviceID *coreDeviceIDs = new AudioDeviceID [numCoreDevices];
673                 err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
674                                                &outSize, (void *) coreDeviceIDs);
675                 if (err == noErr) {
676                         // Look for the CoreAudio device name...
677                         char coreDeviceName[256];
678                         size_t nameSize;
679
680                         for (int i = 0; i < numCoreDevices; i++) {
681
682                                 nameSize = sizeof (coreDeviceName);
683
684                                 /* enforce duplex devices only */
685
686                                 err = AudioDeviceGetPropertyInfo(coreDeviceIDs[i],
687                                                                  0, true, kAudioDevicePropertyStreams,
688                                                                  &outSize, &isWritable);
689
690                                 if (err != noErr || outSize == 0) {
691                                         continue;
692                                 }
693
694                                 err = AudioDeviceGetPropertyInfo(coreDeviceIDs[i],
695                                                                  0, false, kAudioDevicePropertyStreams,
696                                                                  &outSize, &isWritable);
697
698                                 if (err != noErr || outSize == 0) {
699                                         continue;
700                                 }
701
702                                 err = AudioDeviceGetPropertyInfo(coreDeviceIDs[i],
703                                                                  0, true, kAudioDevicePropertyDeviceName,
704                                                                  &outSize, &isWritable);
705                                 if (err == noErr) {
706                                         err = AudioDeviceGetProperty(coreDeviceIDs[i],
707                                                                      0, true, kAudioDevicePropertyDeviceName,
708                                                                      &nameSize, (void *) coreDeviceName);
709                                         if (err == noErr) {
710                                                 char drivername[128];
711
712                                                 // this returns the unique id for the device
713                                                 // that must be used on the commandline for jack
714                                                 
715                                                 if (getDeviceUIDFromID(coreDeviceIDs[i], drivername, sizeof (drivername)) == noErr) {
716                                                         devs.push_back (coreDeviceName);
717                                                         backend_devs.push_back (drivername);
718                                                 } 
719                                         }
720                                 }
721                         }
722                 }
723                 delete [] coreDeviceIDs;
724         }
725
726
727         if (devs.size() == 0) {
728                 MessageDialog msg (_("\
729 You do not have any audio devices capable of\n\
730 simultaneous playback and recording.\n\n\
731 Please use Applications -> Utilities -> Audio MIDI Setup\n\
732 to create an \"aggregrate\" device, or install a suitable\n\
733 audio interface.\n\n\
734 Please send email to Apple and ask them why new Macs\n\
735 have no duplex audio device.\n\n\
736 Alternatively, if you really want just playback\n\
737 or recording but not both, start JACK before running\n\
738 Ardour and choose the relevant device then."
739                                            ), 
740                                    true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);
741                 msg.set_title (_("No suitable audio devices"));
742                 msg.set_position (Gtk::WIN_POS_MOUSE);
743                 msg.run ();
744                 exit (1);
745         }
746
747
748         return devs;
749 }
750 #else
751 vector<string>
752 EngineControl::enumerate_alsa_devices ()
753 {
754         vector<string> devs;
755
756         snd_ctl_t *handle;
757         snd_ctl_card_info_t *info;
758         snd_pcm_info_t *pcminfo;
759         snd_ctl_card_info_alloca(&info);
760         snd_pcm_info_alloca(&pcminfo);
761         string devname;
762         int cardnum = -1;
763         int device = -1;
764
765         backend_devs.clear ();
766
767         while (snd_card_next (&cardnum) >= 0 && cardnum >= 0) {
768
769                 devname = "hw:";
770                 devname += to_string (cardnum, std::dec);
771
772                 if (snd_ctl_open (&handle, devname.c_str(), 0) >= 0 && snd_ctl_card_info (handle, info) >= 0) {
773
774                         while (snd_ctl_pcm_next_device (handle, &device) >= 0 && device >= 0) {
775
776                                 bool have_playback = false;
777                                 bool have_capture = false;
778
779                                 /* find duplex devices only */
780
781                                 snd_pcm_info_set_device (pcminfo, device);
782                                 snd_pcm_info_set_subdevice (pcminfo, 0);
783                                 snd_pcm_info_set_stream (pcminfo, SND_PCM_STREAM_CAPTURE);
784
785                                 if (snd_ctl_pcm_info (handle, pcminfo) >= 0) {
786                                         have_capture = true;
787                                 }
788
789                                 snd_pcm_info_set_device (pcminfo, device);
790                                 snd_pcm_info_set_subdevice (pcminfo, 0);
791                                 snd_pcm_info_set_stream (pcminfo, SND_PCM_STREAM_PLAYBACK);
792
793                                 if (snd_ctl_pcm_info (handle, pcminfo) >= 0) {
794                                         have_playback = true;
795                                 }
796
797                                 if (have_capture && have_playback) {
798                                         devs.push_back (snd_pcm_info_get_name (pcminfo));
799                                         devname += ',';
800                                         devname += to_string (device, std::dec);
801                                         backend_devs.push_back (devname);
802                                 }
803                         }
804
805                         snd_ctl_close(handle);
806                 }
807         }
808
809         return devs;
810 }
811
812 vector<string>
813 EngineControl::enumerate_ffado_devices ()
814 {
815         vector<string> devs;
816         backend_devs.clear ();
817         return devs;
818 }
819
820 vector<string>
821 EngineControl::enumerate_oss_devices ()
822 {
823         vector<string> devs;
824         return devs;
825 }
826 vector<string>
827 EngineControl::enumerate_dummy_devices ()
828 {
829         vector<string> devs;
830         return devs;
831 }
832 vector<string>
833 EngineControl::enumerate_netjack_devices ()
834 {
835         vector<string> devs;
836         return devs;
837 }
838 #endif
839
840 void
841 EngineControl::driver_changed ()
842 {
843         string driver = driver_combo.get_active_text();
844         string::size_type maxlen = 0;
845         int maxindex = -1;
846         int n = 0;
847
848         enumerate_devices (driver);
849
850         vector<string>& strings = devices[driver];
851
852         if (strings.empty() && driver != "FFADO" && driver != "Dummy") {
853                 error << string_compose (_("No devices found for driver \"%1\""), driver) << endmsg;
854                 return;
855         }
856         
857         for (vector<string>::iterator i = strings.begin(); i != strings.end(); ++i, ++n) {
858                 if ((*i).length() > maxlen) {
859                         maxlen = (*i).length();
860                         maxindex = n;
861                 }
862         }
863
864         set_popdown_strings (interface_combo, strings);
865         set_popdown_strings (input_device_combo, strings);
866         set_popdown_strings (output_device_combo, strings);
867
868         if (!strings.empty()) {
869                 interface_combo.set_active_text (strings.front());
870                 input_device_combo.set_active_text (strings.front());
871                 output_device_combo.set_active_text (strings.front());
872         } 
873         
874         if (driver == "ALSA") {
875                 soft_mode_button.set_sensitive (true);
876                 force16bit_button.set_sensitive (true);
877                 hw_monitor_button.set_sensitive (true);
878                 hw_meter_button.set_sensitive (true);
879                 monitor_button.set_sensitive (true);
880         } else {
881                 soft_mode_button.set_sensitive (false);
882                 force16bit_button.set_sensitive (false);
883                 hw_monitor_button.set_sensitive (false);
884                 hw_meter_button.set_sensitive (false);
885                 monitor_button.set_sensitive (false);
886         }
887 }
888
889 uint32_t
890 EngineControl::get_rate ()
891 {
892         return atoi (sample_rate_combo.get_active_text ());
893 }
894
895 void
896 EngineControl::redisplay_latency ()
897 {
898         uint32_t rate = get_rate();
899 #ifdef __APPLE_
900         float periods = 2;
901 #else
902         float periods = periods_adjustment.get_value();
903 #endif
904         float period_size = atof (period_size_combo.get_active_text());
905
906         char buf[32];
907         snprintf (buf, sizeof(buf), "%.1fmsec", (periods * period_size) / (rate/1000.0));
908
909         latency_label.set_text (buf);
910 }
911
912 void
913 EngineControl::audio_mode_changed ()
914 {
915         Glib::ustring str = audio_mode_combo.get_active_text();
916
917         if (str == _("Playback/Recording on 1 Device")) {
918                 input_device_combo.set_sensitive (false);
919                 output_device_combo.set_sensitive (false);
920         } else if (str == _("Playback/Recording on 2 Devices")) {
921                 input_device_combo.set_sensitive (true);
922                 output_device_combo.set_sensitive (true);
923         } else if (str == _("Playback only")) {
924                 output_device_combo.set_sensitive (true);
925         } else if (str == _("Recording only")) {
926                 input_device_combo.set_sensitive (true);
927         }
928 }
929
930 static bool jack_server_filter(const string& str, void *arg)
931 {
932    return str == "jackd" || str == "jackdmp";
933 }
934
935 void
936 EngineControl::find_jack_servers (vector<string>& strings)
937 {
938 #ifdef __APPLE__
939         /* this magic lets us finds the path to the OSX bundle, and then
940            we infer JACK's location from there
941         */
942         
943         char execpath[MAXPATHLEN+1];
944         uint32_t pathsz = sizeof (execpath);
945
946         _NSGetExecutablePath (execpath, &pathsz);
947         
948         string path (Glib::path_get_dirname (execpath));
949         path += "/jackd";
950
951         if (Glib::file_test (path, FILE_TEST_EXISTS)) {
952                 strings.push_back (path);
953         } 
954
955         if (getenv ("ARDOUR_WITH_JACK")) {
956                 /* no other options - only use the JACK we supply */
957                 if (strings.empty()) {
958                         fatal << _("JACK appears to be missing from the Ardour bundle") << endmsg;
959                         /*NOTREACHED*/
960                 }
961                 return;
962         }
963 #else
964         string path;
965 #endif
966         
967         PathScanner scanner;
968         vector<string *> *jack_servers;
969         std::map<string,int> un;
970         char *p;
971         bool need_minimal_path = false;
972
973         p = getenv ("PATH");
974
975         if (p && *p) {
976                 path = p;
977         } else {
978                 need_minimal_path = true;
979         }
980
981 #ifdef __APPLE__
982         // many mac users don't have PATH set up to include
983         // likely installed locations of JACK
984         need_minimal_path = true;
985 #endif
986
987         if (need_minimal_path) {
988                 if (path.empty()) {
989                         path = "/usr/bin:/bin:/usr/local/bin:/opt/local/bin";
990                 } else {
991                         path += ":/usr/local/bin:/opt/local/bin";
992                 }
993         }
994
995 #ifdef __APPLE__
996         // push it back into the environment so that auto-started JACK can find it.
997         // XXX why can't we just expect OS X users to have PATH set correctly? we can't ...
998         setenv ("PATH", path.c_str(), 1);
999 #endif
1000
1001         jack_servers = scanner (path, jack_server_filter, 0, false, true);
1002         
1003         vector<string *>::iterator iter;
1004         
1005         for (iter = jack_servers->begin(); iter != jack_servers->end(); iter++) {
1006                 string p = **iter;
1007                 
1008                 if (un[p]++ == 0) {
1009                         strings.push_back(p);
1010                 }
1011         }
1012 }
1013
1014 string
1015 EngineControl::get_device_name (const string& driver, const string& human_readable)
1016 {
1017         vector<string>::iterator n;
1018         vector<string>::iterator i;
1019
1020         if (human_readable.empty()) {
1021                 /* this can happen if the user's .ardourrc file has a device name from
1022                    another computer system in it
1023                 */
1024                 MessageDialog msg (_("You need to choose an audio device first."));
1025                 msg.run ();
1026                 return string();
1027         }
1028
1029         if (backend_devs.empty()) {
1030                 return human_readable;
1031         }
1032         
1033         for (i = devices[driver].begin(), n = backend_devs.begin(); i != devices[driver].end(); ++i, ++n) {
1034                 if (human_readable == (*i)) {
1035                         return (*n);
1036                 }
1037         }
1038         
1039         if (i == devices[driver].end()) {
1040                 warning << string_compose (_("Audio device \"%1\" not known on this computer."), human_readable) << endmsg;
1041         }
1042
1043         return string();
1044 }
1045
1046 XMLNode&
1047 EngineControl::get_state ()
1048 {
1049         XMLNode* root = new XMLNode ("AudioSetup");
1050         XMLNode* child;
1051         Glib::ustring path;
1052
1053         child = new XMLNode ("periods");
1054         child->add_property ("val", to_string (periods_adjustment.get_value(), std::dec));
1055         root->add_child_nocopy (*child);
1056
1057         child = new XMLNode ("priority");
1058         child->add_property ("val", to_string (priority_adjustment.get_value(), std::dec));
1059         root->add_child_nocopy (*child);
1060
1061         child = new XMLNode ("ports");
1062         child->add_property ("val", to_string (ports_adjustment.get_value(), std::dec));
1063         root->add_child_nocopy (*child);
1064
1065         child = new XMLNode ("inchannels");
1066         child->add_property ("val", to_string (input_channels.get_value(), std::dec));
1067         root->add_child_nocopy (*child);
1068
1069         child = new XMLNode ("outchannels");
1070         child->add_property ("val", to_string (output_channels.get_value(), std::dec));
1071         root->add_child_nocopy (*child);
1072
1073         child = new XMLNode ("inlatency");
1074         child->add_property ("val", to_string (input_latency.get_value(), std::dec));
1075         root->add_child_nocopy (*child);
1076
1077         child = new XMLNode ("outlatency");
1078         child->add_property ("val", to_string (output_latency.get_value(), std::dec));
1079         root->add_child_nocopy (*child);
1080
1081         child = new XMLNode ("realtime");
1082         child->add_property ("val", to_string (realtime_button.get_active(), std::dec));
1083         root->add_child_nocopy (*child);
1084
1085         child = new XMLNode ("nomemorylock");
1086         child->add_property ("val", to_string (no_memory_lock_button.get_active(), std::dec));
1087         root->add_child_nocopy (*child);
1088
1089         child = new XMLNode ("unlockmemory");
1090         child->add_property ("val", to_string (unlock_memory_button.get_active(), std::dec));
1091         root->add_child_nocopy (*child);
1092
1093         child = new XMLNode ("softmode");
1094         child->add_property ("val", to_string (soft_mode_button.get_active(), std::dec));
1095         root->add_child_nocopy (*child);
1096
1097         child = new XMLNode ("force16bit");
1098         child->add_property ("val", to_string (force16bit_button.get_active(), std::dec));
1099         root->add_child_nocopy (*child);
1100
1101         child = new XMLNode ("hwmonitor");
1102         child->add_property ("val", to_string (hw_monitor_button.get_active(), std::dec));
1103         root->add_child_nocopy (*child);
1104
1105         child = new XMLNode ("hwmeter");
1106         child->add_property ("val", to_string (hw_meter_button.get_active(), std::dec));
1107         root->add_child_nocopy (*child);
1108
1109         child = new XMLNode ("verbose");
1110         child->add_property ("val", to_string (verbose_output_button.get_active(), std::dec));
1111         root->add_child_nocopy (*child);
1112
1113         child = new XMLNode ("samplerate");
1114         child->add_property ("val", sample_rate_combo.get_active_text());
1115         root->add_child_nocopy (*child);
1116
1117         child = new XMLNode ("periodsize");
1118         child->add_property ("val", period_size_combo.get_active_text());
1119         root->add_child_nocopy (*child);
1120
1121         child = new XMLNode ("serverpath");
1122         child->add_property ("val", serverpath_combo.get_active_text());
1123         root->add_child_nocopy (*child);
1124
1125         child = new XMLNode ("driver");
1126         child->add_property ("val", driver_combo.get_active_text());
1127         root->add_child_nocopy (*child);
1128
1129         child = new XMLNode ("interface");
1130         child->add_property ("val", interface_combo.get_active_text());
1131         root->add_child_nocopy (*child);
1132
1133         child = new XMLNode ("timeout");
1134         child->add_property ("val", timeout_combo.get_active_text());
1135         root->add_child_nocopy (*child);
1136
1137         child = new XMLNode ("dither");
1138         child->add_property ("val", dither_mode_combo.get_active_text());
1139         root->add_child_nocopy (*child);
1140
1141         child = new XMLNode ("audiomode");
1142         child->add_property ("val", audio_mode_combo.get_active_text());
1143         root->add_child_nocopy (*child);
1144
1145         child = new XMLNode ("inputdevice");
1146         child->add_property ("val", input_device_combo.get_active_text());
1147         root->add_child_nocopy (*child);
1148
1149         child = new XMLNode ("outputdevice");
1150         child->add_property ("val", output_device_combo.get_active_text());
1151         root->add_child_nocopy (*child);
1152         
1153         return *root;
1154 }
1155
1156 void
1157 EngineControl::set_state (const XMLNode& root)
1158 {
1159         XMLNodeList          clist;
1160         XMLNodeConstIterator citer;
1161         XMLNode* child;
1162         XMLProperty* prop = NULL;
1163         bool using_dummy = false;
1164         
1165         int val;
1166         string strval;
1167         
1168         if ( (child = root.child ("driver"))){
1169                 prop = child->property("val");
1170                 if (prop && (prop->value() == "Dummy") ) {
1171                         using_dummy = true;
1172                 }
1173         }
1174         
1175         clist = root.children();
1176
1177         for (citer = clist.begin(); citer != clist.end(); ++citer) {
1178
1179                 if (prop && (prop->value() == "FFADO" )) {
1180                         continue;
1181                 }
1182
1183                 child = *citer;
1184
1185                 prop = child->property ("val");
1186
1187                 if (!prop || prop->value().empty()) {
1188
1189                         if (using_dummy && ( child->name() == "interface" || child->name() == "inputdevice" || child->name() == "outputdevice" )) {
1190                                 continue;
1191                         }
1192
1193                         error << string_compose (_("AudioSetup value for %1 is missing data"), child->name()) << endmsg;
1194                         continue;
1195                 }
1196                 
1197                 strval = prop->value();
1198
1199                 /* adjustments/spinners */
1200
1201                 if (child->name() == "periods") {
1202                         val = atoi (strval);
1203                         periods_adjustment.set_value(val);
1204                 } else if (child->name() == "priority") {
1205                         val = atoi (strval);
1206                         priority_adjustment.set_value(val);
1207                 } else if (child->name() == "ports") {
1208                         val = atoi (strval);
1209                         ports_adjustment.set_value(val);
1210                 } else if (child->name() == "inchannels") {
1211                         val = atoi (strval);
1212                         input_channels.set_value(val);
1213                 } else if (child->name() == "outchannels") {
1214                         val = atoi (strval);
1215                         output_channels.set_value(val);
1216                 } else if (child->name() == "inlatency") {
1217                         val = atoi (strval);
1218                         input_latency.set_value(val);
1219                 } else if (child->name() == "outlatency") {
1220                         val = atoi (strval);
1221                         output_latency.set_value(val);
1222                 }
1223
1224                 /* buttons */
1225
1226                 else if (child->name() == "realtime") {
1227                         val = atoi (strval);
1228                         realtime_button.set_active(val);
1229                 } else if (child->name() == "nomemorylock") {
1230                         val = atoi (strval);
1231                         no_memory_lock_button.set_active(val);
1232                 } else if (child->name() == "unlockmemory") {
1233                         val = atoi (strval);
1234                         unlock_memory_button.set_active(val);
1235                 } else if (child->name() == "softmode") {
1236                         val = atoi (strval);
1237                         soft_mode_button.set_active(val);
1238                 } else if (child->name() == "force16bit") {
1239                         val = atoi (strval);
1240                         force16bit_button.set_active(val);
1241                 } else if (child->name() == "hwmonitor") {
1242                         val = atoi (strval);
1243                         hw_monitor_button.set_active(val);
1244                 } else if (child->name() == "hwmeter") {
1245                         val = atoi (strval);
1246                         hw_meter_button.set_active(val);
1247                 } else if (child->name() == "verbose") {
1248                         val = atoi (strval);
1249                         verbose_output_button.set_active(val);
1250                 }
1251
1252                 /* combos */
1253
1254                 else if (child->name() == "samplerate") {
1255                         sample_rate_combo.set_active_text(strval);
1256                 } else if (child->name() == "periodsize") {
1257                         period_size_combo.set_active_text(strval);
1258                 } else if (child->name() == "serverpath") {
1259
1260                         /* only attempt to set this if we have bothered to look
1261                            up server names already. otherwise this is all
1262                            redundant (actually, all of this dialog/widget 
1263                            is redundant in that case ...)
1264                         */
1265                        
1266
1267                         if (!server_strings.empty()) {
1268                                 /* do not allow us to use a server path that doesn't
1269                                    exist on this system. this handles cases where
1270                                    the user has an RC file listing a serverpath
1271                                    from some other machine.
1272                                 */
1273                                 vector<string>::iterator x;
1274                                 for (x = server_strings.begin(); x != server_strings.end(); ++x) {
1275                                         if (*x == strval) {
1276                                                 break;
1277                                         }
1278                                 }
1279                                 if (x != server_strings.end()) {
1280                                         serverpath_combo.set_active_text (strval);
1281                                 } else {
1282                                         warning << string_compose (_("configuration files contain a JACK server path that doesn't exist (%1)"),
1283                                                                    strval)
1284                                                 << endmsg;
1285                                 }
1286                         }
1287
1288                 } else if (child->name() == "driver") {
1289                         driver_combo.set_active_text(strval);
1290                 } else if (child->name() == "interface") {
1291                         interface_combo.set_active_text(strval);
1292                         if (!strval.empty()) {
1293                                 _interface_chosen = true;
1294                         }
1295                 } else if (child->name() == "timeout") {
1296                         timeout_combo.set_active_text(strval);
1297                 } else if (child->name() == "dither") {
1298                         dither_mode_combo.set_active_text(strval);
1299                 } else if (child->name() == "audiomode") {
1300                         audio_mode_combo.set_active_text(strval);
1301                 } else if (child->name() == "inputdevice") {
1302                         input_device_combo.set_active_text(strval);
1303                 } else if (child->name() == "outputdevice") {
1304                         output_device_combo.set_active_text(strval);
1305                 }
1306         }
1307 }