a boatload of changes and fixes related to SAE menu redesign (several new operations...
[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         cerr << "JACK COMMAND: ";
557         for (vector<string>::iterator i = args.begin(); i != args.end(); ++i) {
558                 cerr << (*i) << ' ';
559                 jackdrc << (*i) << ' ';
560         }
561         cerr << endl;
562         jackdrc << endl;
563         jackdrc.close ();
564
565         _used = true;
566
567         return 0;
568 }
569
570 void
571 EngineControl::realtime_changed ()
572 {
573 #ifndef __APPLE__
574         priority_spinner.set_sensitive (realtime_button.get_active());
575 #endif
576 }
577
578 void
579 EngineControl::enumerate_devices (const string& driver)
580 {
581         /* note: case matters for the map keys */
582
583         if (driver == "CoreAudio") {
584 #ifdef __APPLE__                
585                 devices[driver] = enumerate_coreaudio_devices ();
586 #endif
587
588 #ifndef __APPLE__
589         } else if (driver == "ALSA") {
590                 devices[driver] = enumerate_alsa_devices ();
591         } else if (driver == "FFADO") {
592                 devices[driver] = enumerate_ffado_devices ();
593         } else if (driver == "OSS") {
594                 devices[driver] = enumerate_oss_devices ();
595         } else if (driver == "Dummy") {
596                 devices[driver] = enumerate_dummy_devices ();
597         } else if (driver == "NetJACK") {
598                 devices[driver] = enumerate_netjack_devices ();
599         }
600 #else
601         }
602 #endif
603 }
604
605 #ifdef __APPLE__
606 static OSStatus 
607 getDeviceUIDFromID( AudioDeviceID id, char *name, size_t nsize)
608 {
609         UInt32 size = sizeof(CFStringRef);
610         CFStringRef UI;
611         OSStatus res = AudioDeviceGetProperty(id, 0, false,
612                 kAudioDevicePropertyDeviceUID, &size, &UI);
613         if (res == noErr) 
614                 CFStringGetCString(UI,name,nsize,CFStringGetSystemEncoding());
615         CFRelease(UI);
616         return res;
617 }
618
619 vector<string>
620 EngineControl::enumerate_coreaudio_devices ()
621 {
622         vector<string> devs;
623         
624         // Find out how many Core Audio devices are there, if any...
625         // (code snippet gently "borrowed" from St?hane Letz jackdmp;)
626         OSStatus err;
627         Boolean isWritable;
628         size_t outSize = sizeof(isWritable);
629
630         backend_devs.clear ();
631
632         err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
633                                            &outSize, &isWritable);
634         if (err == noErr) {
635                 // Calculate the number of device available...
636                 int numCoreDevices = outSize / sizeof(AudioDeviceID);
637                 // Make space for the devices we are about to get...
638                 AudioDeviceID *coreDeviceIDs = new AudioDeviceID [numCoreDevices];
639                 err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
640                                                &outSize, (void *) coreDeviceIDs);
641                 if (err == noErr) {
642                         // Look for the CoreAudio device name...
643                         char coreDeviceName[256];
644                         size_t nameSize;
645
646                         for (int i = 0; i < numCoreDevices; i++) {
647
648                                 nameSize = sizeof (coreDeviceName);
649
650                                 /* enforce duplex devices only */
651
652                                 err = AudioDeviceGetPropertyInfo(coreDeviceIDs[i],
653                                                                  0, true, kAudioDevicePropertyStreams,
654                                                                  &outSize, &isWritable);
655
656                                 if (err != noErr || outSize == 0) {
657                                         continue;
658                                 }
659
660                                 err = AudioDeviceGetPropertyInfo(coreDeviceIDs[i],
661                                                                  0, false, kAudioDevicePropertyStreams,
662                                                                  &outSize, &isWritable);
663
664                                 if (err != noErr || outSize == 0) {
665                                         continue;
666                                 }
667
668                                 err = AudioDeviceGetPropertyInfo(coreDeviceIDs[i],
669                                                                  0, true, kAudioDevicePropertyDeviceName,
670                                                                  &outSize, &isWritable);
671                                 if (err == noErr) {
672                                         err = AudioDeviceGetProperty(coreDeviceIDs[i],
673                                                                      0, true, kAudioDevicePropertyDeviceName,
674                                                                      &nameSize, (void *) coreDeviceName);
675                                         if (err == noErr) {
676                                                 char drivername[128];
677
678                                                 // this returns the unique id for the device
679                                                 // that must be used on the commandline for jack
680                                                 
681                                                 if (getDeviceUIDFromID(coreDeviceIDs[i], drivername, sizeof (drivername)) == noErr) {
682                                                         devs.push_back (coreDeviceName);
683                                                         backend_devs.push_back (drivername);
684                                                 } 
685                                         }
686                                 }
687                         }
688                 }
689                 delete [] coreDeviceIDs;
690         }
691
692
693         if (devs.size() == 0) {
694                 MessageDialog msg (_("\
695 You do not have any audio devices capable of\n\
696 simultaneous playback and recording.\n\n\
697 Please use Applications -> Utilities -> Audio MIDI Setup\n\
698 to create an \"aggregrate\" device, or install a suitable\n\
699 audio interface.\n\n\
700 Please send email to Apple and ask them why new Macs\n\
701 have no duplex audio device.\n\n\
702 Alternatively, if you really want just playback\n\
703 or recording but not both, start JACK before running\n\
704 Ardour and choose the relevant device then."
705                                            ), 
706                                    true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);
707                 msg.set_title (_("No suitable audio devices"));
708                 msg.set_position (Gtk::WIN_POS_MOUSE);
709                 msg.run ();
710                 exit (1);
711         }
712
713
714         return devs;
715 }
716 #else
717 vector<string>
718 EngineControl::enumerate_alsa_devices ()
719 {
720         vector<string> devs;
721
722         snd_ctl_t *handle;
723         snd_ctl_card_info_t *info;
724         snd_pcm_info_t *pcminfo;
725         snd_ctl_card_info_alloca(&info);
726         snd_pcm_info_alloca(&pcminfo);
727         string devname;
728         int cardnum = -1;
729         int device = -1;
730
731         backend_devs.clear ();
732
733         while (snd_card_next (&cardnum) >= 0 && cardnum >= 0) {
734
735                 devname = "hw:";
736                 devname += to_string (cardnum, std::dec);
737
738                 if (snd_ctl_open (&handle, devname.c_str(), 0) >= 0 && snd_ctl_card_info (handle, info) >= 0) {
739
740                         while (snd_ctl_pcm_next_device (handle, &device) >= 0 && device >= 0) {
741
742                                 bool have_playback = false;
743                                 bool have_capture = false;
744
745                                 /* find duplex devices only */
746
747                                 snd_pcm_info_set_device (pcminfo, device);
748                                 snd_pcm_info_set_subdevice (pcminfo, 0);
749                                 snd_pcm_info_set_stream (pcminfo, SND_PCM_STREAM_CAPTURE);
750
751                                 if (snd_ctl_pcm_info (handle, pcminfo) >= 0) {
752                                         have_capture = true;
753                                 }
754
755                                 snd_pcm_info_set_device (pcminfo, device);
756                                 snd_pcm_info_set_subdevice (pcminfo, 0);
757                                 snd_pcm_info_set_stream (pcminfo, SND_PCM_STREAM_PLAYBACK);
758
759                                 if (snd_ctl_pcm_info (handle, pcminfo) >= 0) {
760                                         have_playback = true;
761                                 }
762
763                                 if (have_capture && have_playback) {
764                                         devs.push_back (snd_pcm_info_get_name (pcminfo));
765                                         devname += ',';
766                                         devname += to_string (device, std::dec);
767                                         backend_devs.push_back (devname);
768                                 }
769                         }
770
771                         snd_ctl_close(handle);
772                 }
773         }
774
775         return devs;
776 }
777
778 vector<string>
779 EngineControl::enumerate_ffado_devices ()
780 {
781         vector<string> devs;
782         backend_devs.clear ();
783         return devs;
784 }
785
786 vector<string>
787 EngineControl::enumerate_oss_devices ()
788 {
789         vector<string> devs;
790         return devs;
791 }
792 vector<string>
793 EngineControl::enumerate_dummy_devices ()
794 {
795         vector<string> devs;
796         return devs;
797 }
798 vector<string>
799 EngineControl::enumerate_netjack_devices ()
800 {
801         vector<string> devs;
802         return devs;
803 }
804 #endif
805
806 void
807 EngineControl::driver_changed ()
808 {
809         string driver = driver_combo.get_active_text();
810         string::size_type maxlen = 0;
811         int maxindex = -1;
812         int n = 0;
813
814         enumerate_devices (driver);
815
816         vector<string>& strings = devices[driver];
817
818         if (strings.empty() && driver != "FFADO" && driver != "Dummy") {
819                 error << string_compose (_("No devices found for driver \"%1\""), driver) << endmsg;
820                 return;
821         }
822         
823         for (vector<string>::iterator i = strings.begin(); i != strings.end(); ++i, ++n) {
824                 if ((*i).length() > maxlen) {
825                         maxlen = (*i).length();
826                         maxindex = n;
827                 }
828         }
829
830         set_popdown_strings (interface_combo, strings);
831         set_popdown_strings (input_device_combo, strings);
832         set_popdown_strings (output_device_combo, strings);
833
834         if (!strings.empty()) {
835                 interface_combo.set_active_text (strings.front());
836                 input_device_combo.set_active_text (strings.front());
837                 output_device_combo.set_active_text (strings.front());
838         } 
839         
840         if (driver == "ALSA") {
841                 soft_mode_button.set_sensitive (true);
842                 force16bit_button.set_sensitive (true);
843                 hw_monitor_button.set_sensitive (true);
844                 hw_meter_button.set_sensitive (true);
845                 monitor_button.set_sensitive (true);
846         } else {
847                 soft_mode_button.set_sensitive (false);
848                 force16bit_button.set_sensitive (false);
849                 hw_monitor_button.set_sensitive (false);
850                 hw_meter_button.set_sensitive (false);
851                 monitor_button.set_sensitive (false);
852         }
853 }
854
855 uint32_t
856 EngineControl::get_rate ()
857 {
858         return atoi (sample_rate_combo.get_active_text ());
859 }
860
861 void
862 EngineControl::redisplay_latency ()
863 {
864         uint32_t rate = get_rate();
865 #ifdef __APPLE_
866         float periods = 2;
867 #else
868         float periods = periods_adjustment.get_value();
869 #endif
870         float period_size = atof (period_size_combo.get_active_text());
871
872         char buf[32];
873         snprintf (buf, sizeof(buf), "%.1fmsec", (periods * period_size) / (rate/1000.0));
874
875         latency_label.set_text (buf);
876 }
877
878 void
879 EngineControl::audio_mode_changed ()
880 {
881         Glib::ustring str = audio_mode_combo.get_active_text();
882
883         if (str == _("Playback/Recording on 1 Device")) {
884                 input_device_combo.set_sensitive (false);
885                 output_device_combo.set_sensitive (false);
886         } else if (str == _("Playback/Recording on 2 Devices")) {
887                 input_device_combo.set_sensitive (true);
888                 output_device_combo.set_sensitive (true);
889         } else if (str == _("Playback only")) {
890                 output_device_combo.set_sensitive (true);
891         } else if (str == _("Recording only")) {
892                 input_device_combo.set_sensitive (true);
893         }
894 }
895
896 static bool jack_server_filter(const string& str, void *arg)
897 {
898    return str == "jackd" || str == "jackdmp";
899 }
900
901 void
902 EngineControl::find_jack_servers (vector<string>& strings)
903 {
904 #ifdef __APPLE__
905         /* this magic lets us finds the path to the OSX bundle, and then
906            we infer JACK's location from there
907         */
908         
909         char execpath[MAXPATHLEN+1];
910         uint32_t pathsz = sizeof (execpath);
911
912         _NSGetExecutablePath (execpath, &pathsz);
913         
914         string path (Glib::path_get_dirname (execpath));
915         path += "/jackd";
916
917         if (Glib::file_test (path, FILE_TEST_EXISTS)) {
918                 strings.push_back (path);
919         } 
920
921         if (getenv ("ARDOUR_WITH_JACK")) {
922                 /* no other options - only use the JACK we supply */
923                 if (strings.empty()) {
924                         fatal << _("JACK appears to be missing from the Ardour bundle") << endmsg;
925                         /*NOTREACHED*/
926                 }
927                 return;
928         }
929 #else
930         string path;
931 #endif
932         
933         PathScanner scanner;
934         vector<string *> *jack_servers;
935         std::map<string,int> un;
936         char *p;
937         bool need_minimal_path = false;
938
939         p = getenv ("PATH");
940
941         if (p && *p) {
942                 path = p;
943         } else {
944                 need_minimal_path = true;
945         }
946
947 #ifdef __APPLE__
948         // many mac users don't have PATH set up to include
949         // likely installed locations of JACK
950         need_minimal_path = true;
951 #endif
952
953         if (need_minimal_path) {
954                 if (path.empty()) {
955                         path = "/usr/bin:/bin:/usr/local/bin:/opt/local/bin";
956                 } else {
957                         path += ":/usr/local/bin:/opt/local/bin";
958                 }
959         }
960
961 #ifdef __APPLE__
962         // push it back into the environment so that auto-started JACK can find it.
963         // XXX why can't we just expect OS X users to have PATH set correctly? we can't ...
964         setenv ("PATH", path.c_str(), 1);
965 #endif
966
967         jack_servers = scanner (path, jack_server_filter, 0, false, true);
968         
969         vector<string *>::iterator iter;
970         
971         for (iter = jack_servers->begin(); iter != jack_servers->end(); iter++) {
972                 string p = **iter;
973                 
974                 if (un[p]++ == 0) {
975                         strings.push_back(p);
976                 }
977         }
978 }
979
980 string
981 EngineControl::get_device_name (const string& driver, const string& human_readable)
982 {
983         vector<string>::iterator n;
984         vector<string>::iterator i;
985
986         if (backend_devs.empty()) {
987                 return human_readable;
988         }
989         
990         for (i = devices[driver].begin(), n = backend_devs.begin(); i != devices[driver].end(); ++i, ++n) {
991                 if (human_readable == (*i)) {
992                         return (*n);
993                 }
994         }
995         
996         if (i == devices[driver].end()) {
997                 fatal << string_compose (_("programming error: %1"), "true hardware name for ID missing") << endmsg;
998                 /*NOTREACHED*/
999         }
1000
1001         /* keep gcc happy */
1002
1003         return string();
1004 }
1005
1006 XMLNode&
1007 EngineControl::get_state ()
1008 {
1009         XMLNode* root = new XMLNode ("AudioSetup");
1010         XMLNode* child;
1011         Glib::ustring path;
1012
1013         child = new XMLNode ("periods");
1014         child->add_property ("val", to_string (periods_adjustment.get_value(), std::dec));
1015         root->add_child_nocopy (*child);
1016
1017         child = new XMLNode ("priority");
1018         child->add_property ("val", to_string (priority_adjustment.get_value(), std::dec));
1019         root->add_child_nocopy (*child);
1020
1021         child = new XMLNode ("ports");
1022         child->add_property ("val", to_string (ports_adjustment.get_value(), std::dec));
1023         root->add_child_nocopy (*child);
1024
1025         child = new XMLNode ("inchannels");
1026         child->add_property ("val", to_string (input_channels.get_value(), std::dec));
1027         root->add_child_nocopy (*child);
1028
1029         child = new XMLNode ("outchannels");
1030         child->add_property ("val", to_string (output_channels.get_value(), std::dec));
1031         root->add_child_nocopy (*child);
1032
1033         child = new XMLNode ("inlatency");
1034         child->add_property ("val", to_string (input_latency.get_value(), std::dec));
1035         root->add_child_nocopy (*child);
1036
1037         child = new XMLNode ("outlatency");
1038         child->add_property ("val", to_string (output_latency.get_value(), std::dec));
1039         root->add_child_nocopy (*child);
1040
1041         child = new XMLNode ("realtime");
1042         child->add_property ("val", to_string (realtime_button.get_active(), std::dec));
1043         root->add_child_nocopy (*child);
1044
1045         child = new XMLNode ("nomemorylock");
1046         child->add_property ("val", to_string (no_memory_lock_button.get_active(), std::dec));
1047         root->add_child_nocopy (*child);
1048
1049         child = new XMLNode ("unlockmemory");
1050         child->add_property ("val", to_string (unlock_memory_button.get_active(), std::dec));
1051         root->add_child_nocopy (*child);
1052
1053         child = new XMLNode ("softmode");
1054         child->add_property ("val", to_string (soft_mode_button.get_active(), std::dec));
1055         root->add_child_nocopy (*child);
1056
1057         child = new XMLNode ("force16bit");
1058         child->add_property ("val", to_string (force16bit_button.get_active(), std::dec));
1059         root->add_child_nocopy (*child);
1060
1061         child = new XMLNode ("hwmonitor");
1062         child->add_property ("val", to_string (hw_monitor_button.get_active(), std::dec));
1063         root->add_child_nocopy (*child);
1064
1065         child = new XMLNode ("hwmeter");
1066         child->add_property ("val", to_string (hw_meter_button.get_active(), std::dec));
1067         root->add_child_nocopy (*child);
1068
1069         child = new XMLNode ("verbose");
1070         child->add_property ("val", to_string (verbose_output_button.get_active(), std::dec));
1071         root->add_child_nocopy (*child);
1072
1073         child = new XMLNode ("samplerate");
1074         child->add_property ("val", sample_rate_combo.get_active_text());
1075         root->add_child_nocopy (*child);
1076
1077         child = new XMLNode ("periodsize");
1078         child->add_property ("val", period_size_combo.get_active_text());
1079         root->add_child_nocopy (*child);
1080
1081         child = new XMLNode ("serverpath");
1082         child->add_property ("val", serverpath_combo.get_active_text());
1083         root->add_child_nocopy (*child);
1084
1085         child = new XMLNode ("driver");
1086         child->add_property ("val", driver_combo.get_active_text());
1087         root->add_child_nocopy (*child);
1088
1089         child = new XMLNode ("interface");
1090         child->add_property ("val", interface_combo.get_active_text());
1091         root->add_child_nocopy (*child);
1092
1093         child = new XMLNode ("timeout");
1094         child->add_property ("val", timeout_combo.get_active_text());
1095         root->add_child_nocopy (*child);
1096
1097         child = new XMLNode ("dither");
1098         child->add_property ("val", dither_mode_combo.get_active_text());
1099         root->add_child_nocopy (*child);
1100
1101         child = new XMLNode ("audiomode");
1102         child->add_property ("val", audio_mode_combo.get_active_text());
1103         root->add_child_nocopy (*child);
1104
1105         child = new XMLNode ("inputdevice");
1106         child->add_property ("val", input_device_combo.get_active_text());
1107         root->add_child_nocopy (*child);
1108
1109         child = new XMLNode ("outputdevice");
1110         child->add_property ("val", output_device_combo.get_active_text());
1111         root->add_child_nocopy (*child);
1112         
1113         return *root;
1114 }
1115
1116 void
1117 EngineControl::set_state (const XMLNode& root)
1118 {
1119         XMLNodeList          clist;
1120         XMLNodeConstIterator citer;
1121         XMLNode* child;
1122         XMLProperty* prop;
1123         bool using_dummy = false;
1124         
1125         int val;
1126         string strval;
1127         
1128         if ( (child = root.child ("driver"))){
1129                 prop = child->property("val");
1130                 if (prop && (prop->value() == "Dummy") ) {
1131                         using_dummy = true;
1132                 }
1133         }
1134         
1135         clist = root.children();
1136
1137         for (citer = clist.begin(); citer != clist.end(); ++citer) {
1138                 if ( prop && (prop->value() == "FFADO" ))
1139                                 continue;
1140                 child = *citer;
1141
1142                 prop = child->property ("val");
1143
1144                 if (!prop || prop->value().empty()) {
1145
1146                         if ( using_dummy && ( child->name() == "interface" || child->name() == "inputdevice" || child->name() == "outputdevice" ))
1147                                 continue;
1148                         error << string_compose (_("AudioSetup value for %1 is missing data"), child->name()) << endmsg;
1149                         continue;
1150                 }
1151                 
1152                 strval = prop->value();
1153
1154                 /* adjustments/spinners */
1155
1156                 if (child->name() == "periods") {
1157                         val = atoi (strval);
1158                         periods_adjustment.set_value(val);
1159                 } else if (child->name() == "priority") {
1160                         val = atoi (strval);
1161                         priority_adjustment.set_value(val);
1162                 } else if (child->name() == "ports") {
1163                         val = atoi (strval);
1164                         ports_adjustment.set_value(val);
1165                 } else if (child->name() == "inchannels") {
1166                         val = atoi (strval);
1167                         input_channels.set_value(val);
1168                 } else if (child->name() == "outchannels") {
1169                         val = atoi (strval);
1170                         output_channels.set_value(val);
1171                 } else if (child->name() == "inlatency") {
1172                         val = atoi (strval);
1173                         input_latency.set_value(val);
1174                 } else if (child->name() == "outlatency") {
1175                         val = atoi (strval);
1176                         output_latency.set_value(val);
1177                 }
1178
1179                 /* buttons */
1180
1181                 else if (child->name() == "realtime") {
1182                         val = atoi (strval);
1183                         realtime_button.set_active(val);
1184                 } else if (child->name() == "nomemorylock") {
1185                         val = atoi (strval);
1186                         no_memory_lock_button.set_active(val);
1187                 } else if (child->name() == "unlockmemory") {
1188                         val = atoi (strval);
1189                         unlock_memory_button.set_active(val);
1190                 } else if (child->name() == "softmode") {
1191                         val = atoi (strval);
1192                         soft_mode_button.set_active(val);
1193                 } else if (child->name() == "force16bit") {
1194                         val = atoi (strval);
1195                         force16bit_button.set_active(val);
1196                 } else if (child->name() == "hwmonitor") {
1197                         val = atoi (strval);
1198                         hw_monitor_button.set_active(val);
1199                 } else if (child->name() == "hwmeter") {
1200                         val = atoi (strval);
1201                         hw_meter_button.set_active(val);
1202                 } else if (child->name() == "verbose") {
1203                         val = atoi (strval);
1204                         verbose_output_button.set_active(val);
1205                 }
1206
1207                 /* combos */
1208
1209                 else if (child->name() == "samplerate") {
1210                         sample_rate_combo.set_active_text(strval);
1211                 } else if (child->name() == "periodsize") {
1212                         period_size_combo.set_active_text(strval);
1213                 } else if (child->name() == "serverpath") {
1214                         /* do not allow us to use a server path that doesn't
1215                            exist on this system. this handles cases where
1216                            the user has an RC file listing a serverpath
1217                            from some other machine.
1218                         */
1219                         vector<string>::iterator x;
1220                         for (x = server_strings.begin(); x != server_strings.end(); ++x) {
1221                                 if (*x == strval) {
1222                                         break;
1223                                 }
1224                         }
1225                         if (x != server_strings.end()) {
1226                                 serverpath_combo.set_active_text (strval);
1227                         } else {
1228                                 warning << string_compose (_("configuration files contain a JACK server path that doesn't exist (%1)"),
1229                                                              strval)
1230                                         << endmsg;
1231                         }
1232                 } else if (child->name() == "driver") {
1233                         driver_combo.set_active_text(strval);
1234                 } else if (child->name() == "interface") {
1235                         interface_combo.set_active_text(strval);
1236                 } else if (child->name() == "timeout") {
1237                         timeout_combo.set_active_text(strval);
1238                 } else if (child->name() == "dither") {
1239                         dither_mode_combo.set_active_text(strval);
1240                 } else if (child->name() == "audiomode") {
1241                         audio_mode_combo.set_active_text(strval);
1242                 } else if (child->name() == "inputdevice") {
1243                         input_device_combo.set_active_text(strval);
1244                 } else if (child->name() == "outputdevice") {
1245                         output_device_combo.set_active_text(strval);
1246                 }
1247         }
1248 }