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