/* * Copyright (C) 2013-2016 Paul Davis * Copyright (C) 2013-2016 Tim Mayberry * Copyright (C) 2013 Colin Fletcher * Copyright (C) 2014-2015 Robin Gareus * Copyright (C) 2014 John Emmas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifdef HAVE_ALSA #include "ardouralsautil/devicelist.h" #endif #ifdef __APPLE__ #include #include #include #include #endif #ifdef PLATFORM_WINDOWS #include // Needed for #include // 'IShellLink' #include "pbd/windows_special_dirs.h" #endif #if (defined PLATFORM_WINDOWS && defined HAVE_PORTAUDIO) #include #endif #include #include "pbd/gstdio_compat.h" #include #include "pbd/epa.h" #include "pbd/error.h" #include "pbd/string_convert.h" #include "pbd/file_utils.h" #include "pbd/search_path.h" #include "jack_utils.h" #ifdef __APPLE #include #endif #include "pbd/i18n.h" using namespace std; using namespace PBD; namespace ARDOUR { // The pretty driver names const char * const portaudio_driver_name = X_("Portaudio"); const char * const coreaudio_driver_name = X_("CoreAudio"); const char * const alsa_driver_name = X_("ALSA"); const char * const oss_driver_name = X_("OSS"); const char * const sun_driver_name = X_("Sun"); const char * const freebob_driver_name = X_("FreeBoB"); const char * const ffado_driver_name = X_("FFADO"); const char * const netjack_driver_name = X_("NetJACK"); const char * const dummy_driver_name = X_("Dummy"); } namespace { // The real driver names const char * const portaudio_driver_command_line_name = X_("portaudio"); const char * const coreaudio_driver_command_line_name = X_("coreaudio"); const char * const alsa_driver_command_line_name = X_("alsa"); const char * const oss_driver_command_line_name = X_("oss"); const char * const sun_driver_command_line_name = X_("sun"); const char * const freebob_driver_command_line_name = X_("freebob"); const char * const ffado_driver_command_line_name = X_("firewire"); const char * const netjack_driver_command_line_name = X_("netjack"); const char * const dummy_driver_command_line_name = X_("dummy"); // should we provide more "pretty" names like above? const char * const alsa_seq_midi_driver_name = X_("alsa"); const char * const alsa_raw_midi_driver_name = X_("alsarawmidi"); const char * const alsaseq_midi_driver_name = X_("seq"); const char * const alsaraw_midi_driver_name = X_("raw"); const char * const winmme_midi_driver_name = X_("winmme"); const char * const coremidi_midi_driver_name = X_("coremidi"); // this should probably be translated const char * const default_device_name = X_("Default"); } static ARDOUR::MidiOptions midi_options; std::string get_none_string () { return _("None"); } void ARDOUR::get_jack_audio_driver_names (vector& audio_driver_names) { #ifdef PLATFORM_WINDOWS audio_driver_names.push_back (portaudio_driver_name); #elif __APPLE__ audio_driver_names.push_back (coreaudio_driver_name); #else #ifdef HAVE_ALSA audio_driver_names.push_back (alsa_driver_name); #endif audio_driver_names.push_back (oss_driver_name); #if defined(__NetBSD__) || defined(__sun) audio_driver_names.push_back (sun_driver_name); #endif audio_driver_names.push_back (freebob_driver_name); audio_driver_names.push_back (ffado_driver_name); #endif audio_driver_names.push_back (netjack_driver_name); audio_driver_names.push_back (dummy_driver_name); } void ARDOUR::get_jack_default_audio_driver_name (string& audio_driver_name) { vector drivers; get_jack_audio_driver_names (drivers); audio_driver_name = drivers.front (); } void ARDOUR::get_jack_sample_rate_strings (vector& samplerates) { // do these really need to be translated? samplerates.push_back (_("8000Hz")); samplerates.push_back (_("22050Hz")); samplerates.push_back (_("44100Hz")); samplerates.push_back (_("48000Hz")); samplerates.push_back (_("88200Hz")); samplerates.push_back (_("96000Hz")); samplerates.push_back (_("192000Hz")); } string ARDOUR::get_jack_default_sample_rate () { return _("48000Hz"); } void ARDOUR::get_jack_period_size_strings (std::vector& period_sizes) { period_sizes.push_back ("32"); period_sizes.push_back ("64"); period_sizes.push_back ("128"); period_sizes.push_back ("256"); period_sizes.push_back ("512"); period_sizes.push_back ("1024"); period_sizes.push_back ("2048"); period_sizes.push_back ("4096"); period_sizes.push_back ("8192"); } string ARDOUR::get_jack_default_period_size () { return "1024"; } void ARDOUR::get_jack_dither_mode_strings (const string& driver, vector& dither_modes) { dither_modes.push_back (get_none_string ()); if (driver == alsa_driver_name ) { dither_modes.push_back (_("Triangular")); dither_modes.push_back (_("Rectangular")); dither_modes.push_back (_("Shaped")); } } string ARDOUR::get_jack_default_dither_mode (const string& /*driver*/) { return get_none_string (); } string ARDOUR::get_jack_latency_string (string samplerate, float periods, string period_size) { uint32_t rate = atoi (samplerate); float psize = atof (period_size); char buf[32]; snprintf (buf, sizeof(buf), "%.1fmsec", (periods * psize) / (rate/1000.0)); return buf; } bool get_jack_command_line_audio_driver_name (const string& driver_name, string& command_line_name) { using namespace ARDOUR; if (driver_name == portaudio_driver_name) { command_line_name = portaudio_driver_command_line_name; return true; } else if (driver_name == coreaudio_driver_name) { command_line_name = coreaudio_driver_command_line_name; return true; } else if (driver_name == alsa_driver_name) { command_line_name = alsa_driver_command_line_name; return true; } else if (driver_name == oss_driver_name) { command_line_name = oss_driver_command_line_name; return true; } else if (driver_name == sun_driver_name) { command_line_name = sun_driver_command_line_name; return true; } else if (driver_name == freebob_driver_name) { command_line_name = freebob_driver_command_line_name; return true; } else if (driver_name == ffado_driver_name) { command_line_name = ffado_driver_command_line_name; return true; } else if (driver_name == netjack_driver_name) { command_line_name = netjack_driver_command_line_name; return true; } else if (driver_name == dummy_driver_name) { command_line_name = dummy_driver_command_line_name; return true; } return false; } bool get_jack_command_line_audio_device_name (const string& driver_name, const string& device_name, string& command_line_device_name) { using namespace ARDOUR; device_map_t devices; get_jack_device_names_for_audio_driver (driver_name, devices); for (device_map_t::const_iterator i = devices.begin (); i != devices.end(); ++i) { if (i->first == device_name) { command_line_device_name = i->second; return true; } } return false; } bool get_jack_command_line_dither_mode (const string& dither_mode, string& command_line_dither_mode) { using namespace ARDOUR; if (dither_mode == _("Triangular")) { command_line_dither_mode = "triangular"; return true; } else if (dither_mode == _("Rectangular")) { command_line_dither_mode = "rectangular"; return true; } else if (dither_mode == _("Shaped")) { command_line_dither_mode = "shaped"; return true; } return false; } void ARDOUR::get_jack_alsa_device_names (device_map_t& devices) { #ifdef HAVE_ALSA get_alsa_audio_device_names(devices, HalfDuplexOut); #else /* silence a compiler unused variable warning */ (void) devices; #endif } #ifdef __APPLE__ static OSStatus getDeviceUIDFromID( AudioDeviceID id, char *name, size_t nsize) { UInt32 size = sizeof(CFStringRef); CFStringRef UI; OSStatus res = AudioDeviceGetProperty(id, 0, false, kAudioDevicePropertyDeviceUID, &size, &UI); if (res == noErr) CFStringGetCString(UI,name,nsize,CFStringGetSystemEncoding()); CFRelease(UI); return res; } #endif void ARDOUR::get_jack_coreaudio_device_names (device_map_t& devices) { #ifdef __APPLE__ // Find out how many Core Audio devices are there, if any... // (code snippet gently "borrowed" from St?hane Letz jackdmp;) OSStatus err; Boolean isWritable; UInt32 outSize = sizeof(isWritable); err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &outSize, &isWritable); if (err == noErr) { // Calculate the number of device available... int numCoreDevices = outSize / sizeof(AudioDeviceID); // Make space for the devices we are about to get... AudioDeviceID *coreDeviceIDs = new AudioDeviceID [numCoreDevices]; err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &outSize, (void *) coreDeviceIDs); if (err == noErr) { // Look for the CoreAudio device name... char coreDeviceName[256]; UInt32 nameSize; for (int i = 0; i < numCoreDevices; i++) { nameSize = sizeof (coreDeviceName); /* enforce duplex devices only */ err = AudioDeviceGetPropertyInfo(coreDeviceIDs[i], 0, true, kAudioDevicePropertyStreams, &outSize, &isWritable); if (err != noErr || outSize == 0) { continue; } err = AudioDeviceGetPropertyInfo(coreDeviceIDs[i], 0, false, kAudioDevicePropertyStreams, &outSize, &isWritable); if (err != noErr || outSize == 0) { continue; } err = AudioDeviceGetPropertyInfo(coreDeviceIDs[i], 0, true, kAudioDevicePropertyDeviceName, &outSize, &isWritable); if (err == noErr) { err = AudioDeviceGetProperty(coreDeviceIDs[i], 0, true, kAudioDevicePropertyDeviceName, &nameSize, (void *) coreDeviceName); if (err == noErr) { char drivername[128]; // this returns the unique id for the device // that must be used on the commandline for jack if (getDeviceUIDFromID(coreDeviceIDs[i], drivername, sizeof (drivername)) == noErr) { devices.insert (make_pair (coreDeviceName, drivername)); } } } } } delete [] coreDeviceIDs; } #else /* silence a compiler unused variable warning */ (void) devices; #endif } void ARDOUR::get_jack_portaudio_device_names (device_map_t& devices) { #if (defined PLATFORM_WINDOWS && defined HAVE_PORTAUDIO) if (Pa_Initialize() != paNoError) { return; } for (PaDeviceIndex i = 0; i < Pa_GetDeviceCount (); ++i) { string api_name; string readable_name; string jack_device_name; const PaDeviceInfo* device_info = Pa_GetDeviceInfo(i); if (device_info != NULL) { // it should never be ? api_name = Pa_GetHostApiInfo (device_info->hostApi)->name; readable_name = api_name + " " + device_info->name; jack_device_name = api_name + "::" + device_info->name; devices.insert (make_pair (readable_name, jack_device_name)); } } Pa_Terminate(); #else /* silence a compiler unused variable warning */ (void) devices; #endif } void ARDOUR::get_jack_oss_device_names (device_map_t& devices) { devices.insert (make_pair (default_device_name, default_device_name)); } void ARDOUR::get_jack_sun_device_names (device_map_t& devices) { devices.insert (make_pair (default_device_name, default_device_name)); } void ARDOUR::get_jack_freebob_device_names (device_map_t& devices) { devices.insert (make_pair (default_device_name, default_device_name)); } void ARDOUR::get_jack_ffado_device_names (device_map_t& devices) { devices.insert (make_pair (default_device_name, default_device_name)); } void ARDOUR::get_jack_netjack_device_names (device_map_t& devices) { devices.insert (make_pair (default_device_name, default_device_name)); } void ARDOUR::get_jack_dummy_device_names (device_map_t& devices) { devices.insert (make_pair (default_device_name, default_device_name)); } bool ARDOUR::get_jack_device_names_for_audio_driver (const string& driver_name, device_map_t& devices) { devices.clear(); if (driver_name == portaudio_driver_name) { get_jack_portaudio_device_names (devices); } else if (driver_name == coreaudio_driver_name) { get_jack_coreaudio_device_names (devices); } else if (driver_name == alsa_driver_name) { get_jack_alsa_device_names (devices); } else if (driver_name == oss_driver_name) { get_jack_oss_device_names (devices); } else if (driver_name == sun_driver_name) { get_jack_sun_device_names (devices); } else if (driver_name == freebob_driver_name) { get_jack_freebob_device_names (devices); } else if (driver_name == ffado_driver_name) { get_jack_ffado_device_names (devices); } else if (driver_name == netjack_driver_name) { get_jack_netjack_device_names (devices); } else if (driver_name == dummy_driver_name) { get_jack_dummy_device_names (devices); } return !devices.empty(); } std::vector ARDOUR::get_jack_device_names_for_audio_driver (const string& driver_name) { std::vector readable_names; device_map_t devices; get_jack_device_names_for_audio_driver (driver_name, devices); for (device_map_t::const_iterator i = devices.begin (); i != devices.end(); ++i) { readable_names.push_back (i->first); } return readable_names; } bool ARDOUR::get_jack_audio_driver_supports_two_devices (const string& driver) { return (driver == alsa_driver_name || driver == oss_driver_name || driver == sun_driver_name); } bool ARDOUR::get_jack_audio_driver_supports_latency_adjustment (const string& driver) { return (driver == alsa_driver_name || driver == coreaudio_driver_name || driver == ffado_driver_name || driver == portaudio_driver_name); } bool ARDOUR::get_jack_audio_driver_supports_setting_period_count (const string& driver) { return !(driver == dummy_driver_name || driver == coreaudio_driver_name || driver == portaudio_driver_name); } bool ARDOUR::get_jack_server_application_names (std::vector& server_names) { #ifdef PLATFORM_WINDOWS server_names.push_back ("jackd.exe"); #else server_names.push_back ("jackd"); server_names.push_back ("jackdmp"); #endif return !server_names.empty(); } void ARDOUR::set_path_env_for_jack_autostart (const vector& dirs) { #ifdef __APPLE__ // push it back into the environment so that auto-started JACK can find it. // XXX why can't we just expect OS X users to have PATH set correctly? we can't ... setenv ("PATH", Searchpath(dirs).to_string().c_str(), 1); #else /* silence a compiler unused variable warning */ (void) dirs; #endif } bool ARDOUR::get_jack_server_dir_paths (vector& server_dir_paths) { #ifdef __APPLE__ /* this magic lets us finds the path to the OSX bundle, and then we infer JACK's location from there */ char execpath[MAXPATHLEN+1]; uint32_t pathsz = sizeof (execpath); _NSGetExecutablePath (execpath, &pathsz); server_dir_paths.push_back (Glib::path_get_dirname (execpath)); #endif Searchpath sp(string(g_getenv("PATH"))); #ifdef PLATFORM_WINDOWS std::string reg; /* since https://github.com/jackaudio/jack2/commit/ac62faa9c0268b89e3ea23c0318227612acd8079 */ bool found = PBD::windows_query_registry ("Software\\JACK", "InstallPath", reg); if (!found) { /* special-case jack 1.9.16, "Location" included jackd.exe */ found = PBD::windows_query_registry ("Software\\JACK", "Location", reg); if (found) { reg = Glib::path_get_dirname (reg); } } if (!found) { // If the newer style regkey wasn't found, check for one in the older style... found = PBD::windows_query_registry ("Software\\Jack", "InstPath", reg, HKEY_CURRENT_USER); } if (found) { sp.push_back (reg); } gchar *install_dir = g_win32_get_package_installation_directory_of_module (NULL); if (install_dir) { sp.push_back (install_dir); g_free (install_dir); } #else if (sp.empty()) { sp.push_back ("/usr/bin"); sp.push_back ("/bin"); sp.push_back ("/usr/local/bin"); sp.push_back ("/opt/local/bin"); } #endif std::copy (sp.begin(), sp.end(), std::back_inserter(server_dir_paths)); return !server_dir_paths.empty(); } bool ARDOUR::get_jack_server_paths (const vector& server_dir_paths, const vector& server_names, vector& server_paths) { for (vector::const_iterator i = server_names.begin(); i != server_names.end(); ++i) { find_files_matching_pattern (server_paths, server_dir_paths, *i); } return !server_paths.empty(); } bool ARDOUR::get_jack_server_paths (vector& server_paths) { vector server_dirs; if (!get_jack_server_dir_paths (server_dirs)) { return false; } vector server_names; if (!get_jack_server_application_names (server_names)) { return false; } if (!get_jack_server_paths (server_dirs, server_names, server_paths)) { return false; } return !server_paths.empty(); } bool ARDOUR::get_jack_default_server_path (std::string& server_path) { vector server_paths; if (!get_jack_server_paths (server_paths)) { return false; } server_path = server_paths.front (); return true; } static string quote_string (string str) { /* escape quotes in string */ size_t pos = 0; while ((pos = str.find("\"", pos)) != std::string::npos) { str.replace (pos, 1, "\\\""); pos += 2; } /* and quote the whole string */ return "\"" + str + "\""; } ARDOUR::JackCommandLineOptions::JackCommandLineOptions () : server_path () , timeout(0) , no_mlock(false) , ports_max(128) , realtime(true) , priority(0) , unlock_gui_libs(false) , verbose(false) , temporary(true) , driver() , input_device() , output_device() , num_periods(2) , period_size(1024) , samplerate(48000) , input_latency(0) , output_latency(0) , hardware_metering(false) , hardware_monitoring(false) , dither_mode() , force16_bit(false) , soft_mode(false) , midi_driver() { } bool ARDOUR::get_jack_command_line_string (JackCommandLineOptions& options, string& command_line) { vector args; args.push_back (options.server_path); #ifdef PLATFORM_WINDOWS // must use sync mode on windows args.push_back ("-S"); #endif #if (defined PLATFORM_WINDOWS || defined __APPLE__) // midi systems needs to be added before the audio driver for jack2 if (!options.midi_driver.empty () && options.midi_driver != get_none_string ()) { args.push_back ("-X"); args.push_back (options.midi_driver); } #endif /* XXX hack to enforce qjackctl-like behaviour */ if (options.timeout == 0) { options.timeout = 200; } if (options.timeout) { args.push_back ("-t"); args.push_back (to_string (options.timeout)); } if (options.no_mlock) { args.push_back ("-m"); } args.push_back ("-p"); args.push_back (to_string(options.ports_max)); if (options.realtime) { args.push_back ("-R"); if (options.priority != 0) { args.push_back ("-P"); args.push_back (to_string(options.priority)); } } else { args.push_back ("-r"); } if (options.unlock_gui_libs) { args.push_back ("-u"); } if (options.verbose) { args.push_back ("-v"); } if (options.temporary) { args.push_back ("-T"); } if (options.driver == alsa_driver_name) { if (options.midi_driver == alsa_seq_midi_driver_name) { args.push_back ("-X"); args.push_back ("alsa_midi"); } else if (options.midi_driver == alsa_raw_midi_driver_name) { args.push_back ("-X"); args.push_back ("alsarawmidi"); } } string command_line_driver_name; string command_line_input_device_name; string command_line_output_device_name; if (!get_jack_command_line_audio_driver_name (options.driver, command_line_driver_name)) { return false; } args.push_back ("-d"); args.push_back (command_line_driver_name); if (options.driver != dummy_driver_name) { if (options.output_device.empty() && options.input_device.empty()) { return false; } if (!get_jack_command_line_audio_device_name (options.driver, options.input_device, command_line_input_device_name)) { return false; } if (!get_jack_command_line_audio_device_name (options.driver, options.output_device, command_line_output_device_name)) { return false; } if (options.input_device.empty()) { // playback only if (options.output_device.empty()) { return false; } args.push_back ("-P"); } else if (options.output_device.empty()) { // capture only if (options.input_device.empty()) { return false; } args.push_back ("-C"); } else if (options.input_device != options.output_device) { // capture and playback on two devices if supported if (get_jack_audio_driver_supports_two_devices (options.driver)) { args.push_back ("-C"); args.push_back (command_line_input_device_name); args.push_back ("-P"); args.push_back (command_line_output_device_name); } else { return false; } } if (options.input_channels) { args.push_back ("-i"); args.push_back (to_string (options.input_channels)); } if (options.output_channels) { args.push_back ("-o"); args.push_back (to_string (options.output_channels)); } if (get_jack_audio_driver_supports_setting_period_count (options.driver)) { args.push_back ("-n"); args.push_back (to_string (options.num_periods)); } } else { // jackd dummy backend if (options.input_channels) { args.push_back ("-C"); args.push_back (to_string (options.input_channels)); } if (options.output_channels) { args.push_back ("-P"); args.push_back (to_string (options.output_channels)); } } args.push_back ("-r"); args.push_back (to_string (options.samplerate)); args.push_back ("-p"); args.push_back (to_string (options.period_size)); if (get_jack_audio_driver_supports_latency_adjustment (options.driver)) { if (options.input_latency) { args.push_back ("-I"); args.push_back (to_string (options.input_latency)); } if (options.output_latency) { args.push_back ("-O"); args.push_back (to_string (options.output_latency)); } } if (options.driver != dummy_driver_name) { if (options.input_device == options.output_device && options.input_device != default_device_name) { args.push_back ("-d"); args.push_back (command_line_input_device_name); } } if (options.driver == alsa_driver_name) { if (options.hardware_metering) { args.push_back ("-M"); } if (options.hardware_monitoring) { args.push_back ("-H"); } string command_line_dither_mode; if (get_jack_command_line_dither_mode (options.dither_mode, command_line_dither_mode)) { args.push_back ("-z"); args.push_back (command_line_dither_mode); } if (options.force16_bit) { args.push_back ("-S"); } if (options.soft_mode) { args.push_back ("-s"); } } if (options.driver == alsa_driver_name) { if (options.midi_driver != alsa_seq_midi_driver_name) { if (!options.midi_driver.empty() && options.midi_driver != get_none_string ()) { args.push_back ("-X"); args.push_back (options.midi_driver); } } } ostringstream oss; for (vector::const_iterator i = args.begin(); i != args.end();) { if ((i != args.begin()) && (i->find_first_of(' ') != string::npos)) { // Be aware that (in Windows at least) Jack can't start if we supply a server path // surrounded in quote marks (maybe Jack already does something similar imternally??) // Fortunately, if it exists in our '.jackdrc' file, the server path will always be // its very first entry - so we skip quoting that entry if it did contain spaces. oss << quote_string (*i); } else { oss << *i; } if (++i != args.end()) oss << ' '; } command_line = oss.str(); return true; } string ARDOUR::get_jack_server_config_file_name () { return ".jackdrc"; } std::string ARDOUR::get_jack_server_user_config_dir_path () { return Glib::get_home_dir (); } std::string ARDOUR::get_jack_server_user_config_file_path () { return Glib::build_filename (get_jack_server_user_config_dir_path (), get_jack_server_config_file_name ()); } bool ARDOUR::write_jack_config_file (const std::string& config_file_path, const string& command_line) { if (!g_file_set_contents (config_file_path.c_str(), command_line.c_str(), -1, NULL)) { error << string_compose (_("cannot open JACK rc file %1 to store parameters"), config_file_path) << endmsg; return false; } return true; } vector ARDOUR::enumerate_midi_options () { if (midi_options.empty()) { #ifdef HAVE_ALSA midi_options.push_back (make_pair (_("(legacy) ALSA raw devices"), alsaraw_midi_driver_name)); midi_options.push_back (make_pair (_("(legacy) ALSA sequencer"), alsaseq_midi_driver_name)); midi_options.push_back (make_pair (_("ALSA (JACK1, 0.124 and later)"), alsa_seq_midi_driver_name)); midi_options.push_back (make_pair (_("ALSA (JACK2, 1.9.8 and later)"), alsa_raw_midi_driver_name)); #endif #if (defined PLATFORM_WINDOWS && defined HAVE_PORTAUDIO) /* Windows folks: what name makes sense here? Are there other choices as well ? */ midi_options.push_back (make_pair (_("System MIDI (MME)"), winmme_midi_driver_name)); #endif #ifdef __APPLE__ midi_options.push_back (make_pair (_("CoreMIDI"), coremidi_midi_driver_name)); #endif } vector v; for (MidiOptions::const_iterator i = midi_options.begin(); i != midi_options.end(); ++i) { v.push_back (i->first); } v.push_back (get_none_string()); return v; } int ARDOUR::set_midi_option (ARDOUR::JackCommandLineOptions& options, const string& opt) { if (opt.empty() || opt == get_none_string()) { options.midi_driver = ""; return 0; } for (MidiOptions::const_iterator i = midi_options.begin(); i != midi_options.end(); ++i) { if (i->first == opt) { options.midi_driver = i->second; return 0; } } return -1; }