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