From: Paul Davis Date: Mon, 15 Jul 2013 16:46:35 +0000 (-0400) Subject: Add JACK utility functions in libardour and test X-Git-Tag: 1.0.0~868^2~146^2~11 X-Git-Url: https://main.carlh.net/gitweb/?a=commitdiff_plain;h=a8647faca7d60ba6404239f2ebcff1631028fbad;p=ardour.git Add JACK utility functions in libardour and test This contains much of the code present in the GUI EngineDialog class but refactored with some added windows bits. --- diff --git a/libs/ardour/ardour/jack_utils.h b/libs/ardour/ardour/jack_utils.h new file mode 100644 index 0000000000..353724f079 --- /dev/null +++ b/libs/ardour/ardour/jack_utils.h @@ -0,0 +1,261 @@ +/* + Copyright (C) 2011 Tim Mayberry + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include + +#include +#include +#include + +namespace ARDOUR { + + // Names for the drivers on all possible systems + extern const char * const portaudio_driver_name; + extern const char * const coreaudio_driver_name; + extern const char * const alsa_driver_name; + extern const char * const oss_driver_name; + extern const char * const freebob_driver_name; + extern const char * const ffado_driver_name; + extern const char * const netjack_driver_name; + extern const char * const dummy_driver_name; + + /** + * Get a list of possible JACK audio driver names based on platform + */ + void get_jack_audio_driver_names (std::vector& driver_names); + + /** + * Get the default JACK audio driver based on platform + */ + void get_jack_default_audio_driver_name (std::string& driver_name); + + /** + * Get a list of possible JACK midi driver names based on platform + */ + void get_jack_midi_system_names (const std::string& driver, std::vector& driver_names); + + /** + * Get the default JACK midi driver based on platform + */ + void get_jack_default_midi_system_name (const std::string& driver_name, std::string& midi_system); + + /** + * Get a list of possible samplerates supported be JACK + */ + void get_jack_sample_rate_strings (std::vector& sample_rates); + + /** + * @return The default samplerate + */ + std::string get_jack_default_sample_rate (); + + /** + * @return true if sample rate string was able to be converted + */ + bool get_jack_sample_rate_value_from_string (const std::string& srs, uint32_t& srv); + + /** + * Get a list of possible period sizes supported be JACK + */ + void get_jack_period_size_strings (std::vector& samplerates); + + /** + * @return The default period size + */ + std::string get_jack_default_period_size (); + + /** + * @return true if period size string was able to be converted + */ + bool get_jack_period_size_value_from_string (const std::string& pss, uint32_t& psv); + + /** + * These are driver specific I think, so it may require a driver arg + * in future + */ + void get_jack_dither_mode_strings (const std::string& driver, std::vector& dither_modes); + + /** + * @return The default dither mode + */ + std::string get_jack_default_dither_mode (const std::string& driver); + + /** + * @return Estimate of latency + * + * API matches current use in GUI + */ + std::string get_jack_latency_string (std::string samplerate, float periods, std::string period_size); + + /** + * @return true if a JACK server is running + */ + bool jack_server_running (); + + /** + * Key being a readable name to display in a GUI + * Value being name used in a jack commandline + */ + typedef std::map device_map_t; + + /** + * Use library specific code to find out what what devices exist for a given + * driver that might work in JACK. There is no easy way to find out what + * modules the JACK server supports so guess based on platform. For instance + * portaudio is cross-platform but we only return devices if built for + * windows etc + */ + void get_jack_alsa_device_names (device_map_t& devices); + void get_jack_portaudio_device_names (device_map_t& devices); + void get_jack_coreaudio_device_names (device_map_t& devices); + void get_jack_oss_device_names (device_map_t& devices); + void get_jack_freebob_device_names (device_map_t& devices); + void get_jack_ffado_device_names (device_map_t& devices); + void get_jack_netjack_device_names (device_map_t& devices); + void get_jack_dummy_device_names (device_map_t& devices); + + /* + * @return true if there were devices found for the driver + * + * @param driver The driver name returned by get_jack_audio_driver_names + * @param devices The map used to insert the drivers into, devices will be cleared before + * adding the available drivers + */ + bool get_jack_device_names_for_audio_driver (const std::string& driver, device_map_t& devices); + + /* + * @return a list of readable device names for a specific driver. + */ + std::vector get_jack_device_names_for_audio_driver (const std::string& driver); + + /** + * @return true if the driver supports playback and recording + * on separate devices + */ + bool get_jack_audio_driver_supports_two_devices (const std::string& driver); + + bool get_jack_audio_driver_supports_latency_adjustment (const std::string& driver); + + bool get_jack_audio_driver_supports_setting_period_count (const std::string& driver); + + /** + * The possible names to use to try and find servers, this includes + * any file extensions like .exe on Windows + * + * @return true if the JACK application names for this platform could be guessed + */ + bool get_jack_server_application_names (std::vector& server_names); + + /** + * Sets the PATH environment variable to contain directories likely to contain + * JACK servers so that if the JACK server is auto-started it can find the server + * executable. + * + * This is only modifies PATH on the mac at the moment. + */ + void set_path_env_for_jack_autostart (const std::vector&); + + /** + * Get absolute paths to directories that might contain JACK servers on the system + * + * @return true if !server_paths.empty() + */ + bool get_jack_server_dir_paths (std::vector& server_dir_paths); + + /** + * Get absolute paths to JACK servers on the system + * + * @return true if a server was found + */ + bool get_jack_server_paths (const std::vector& server_dir_paths, + const std::vector& server_names, + std::vector& server_paths); + + + bool get_jack_server_paths (std::vector& server_paths); + + /** + * Get absolute path to default JACK server + */ + bool get_jack_default_server_path (std::string& server_path); + + /** + * @return The name of the jack server config file + */ + std::string get_jack_server_config_file_name (); + + std::string get_jack_server_user_config_dir_path (); + + std::string get_jack_server_user_config_file_path (); + + bool write_jack_config_file (const std::string& config_file_path, const std::string& command_line); + + struct JackCommandLineOptions { + + // see implementation for defaults + JackCommandLineOptions (); + + //operator bool + //operator ostream + + std::string server_path; + uint32_t timeout; + bool no_mlock; + uint32_t ports_max; + bool realtime; + uint32_t priority; + bool unlock_gui_libs; + bool verbose; + bool temporary; + bool playback_only; + bool capture_only; + std::string driver; + std::string input_device; + std::string output_device; + uint32_t num_periods; + uint32_t period_size; + uint32_t samplerate; + uint32_t input_latency; + uint32_t output_latency; + bool hardware_metering; + bool hardware_monitoring; + std::string dither_mode; + bool force16_bit; + bool soft_mode; + std::string midi_driver; + }; + + /** + * @return true if able to build a valid command line based on options + */ + bool get_jack_command_line_string (const JackCommandLineOptions& options, std::string& command_line); + + /** + * We don't need this at the moment because the gui stores all its settings + */ + //std::string get_jack_command_line_from_config_file (const std::string& config_file_path); + + /** + * Temporary for WIN32 only as jack_client_open doesn't start the server on that platform + * + * @return true if server was able to be started + */ + bool start_jack_server (const std::string& command_line); + +} diff --git a/libs/ardour/jack_utils.cc b/libs/ardour/jack_utils.cc new file mode 100644 index 0000000000..29f7ca4f3e --- /dev/null +++ b/libs/ardour/jack_utils.cc @@ -0,0 +1,947 @@ +/* + Copyright (C) 2010 Paul Davis + Copyright (C) 2011 Tim Mayberry + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifdef WAF_BUILD +#include "libardour-config.h" +#endif + +#ifdef HAVE_ALSA +#include +#endif + +#ifdef __APPLE__ +#include +#include +#include +#include +#endif + +#ifdef HAVE_PORTAUDIO +#include +#endif + +#include + +#include + +#include + +#include + +#include "pbd/epa.h" +#include "pbd/error.h" +#include "pbd/convert.h" +#include "pbd/file_utils.h" +#include "pbd/search_path.h" + +#include "ardour/jack_utils.h" + +#ifdef __APPLE +#include +#endif + +#include "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 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 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 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"); +} + +std::string +get_none_string () +{ + return _("None"); +} + +void +ARDOUR::get_jack_audio_driver_names (vector& audio_driver_names) +{ +#ifdef WIN32 + 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); + 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_midi_system_names (const string& driver, vector& midi_system_names) +{ + midi_system_names.push_back (get_none_string ()); +#ifdef WIN32 + midi_system_names.push_back (winmme_midi_driver_name); +#elif __APPLE__ + midi_system_names.push_back (coreaudio_midi_driver_name); +#else +#ifdef HAVE_ALSA + if (driver == alsa_driver_name) { + midi_system_names.push_back (alsaseq_midi_driver_name); + midi_system_names.push_back (alsaraw_midi_driver_name); + } +#endif +#endif +} + +void +ARDOUR::get_jack_default_midi_system_name (const string& driver, string& midi_system_name) +{ + vector drivers; + get_jack_midi_system_names (driver, drivers); + midi_system_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 == 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; +} + +bool +ARDOUR::jack_server_running () +{ + EnvironmentalProtectionAgency* global_epa = EnvironmentalProtectionAgency::get_global_epa (); + boost::scoped_ptr current_epa; + + /* revert all environment settings back to whatever they were when ardour started + */ + + if (global_epa) { + current_epa.reset (new EnvironmentalProtectionAgency(true)); /* will restore settings when we leave scope */ + global_epa->restore (); + } + + jack_status_t status; + jack_client_t* c = jack_client_open ("ardourprobe", JackNoStartServer, &status); + + if (status == 0) { + jack_client_close (c); + return true; + } + return false; + +} + +void +ARDOUR::get_jack_alsa_device_names (device_map_t& devices) +{ +#ifdef HAVE_ALSA + snd_ctl_t *handle; + snd_ctl_card_info_t *info; + snd_pcm_info_t *pcminfo; + snd_ctl_card_info_alloca(&info); + snd_pcm_info_alloca(&pcminfo); + string devname; + int cardnum = -1; + int device = -1; + + while (snd_card_next (&cardnum) >= 0 && cardnum >= 0) { + + devname = "hw:"; + devname += PBD::to_string (cardnum, std::dec); + + if (snd_ctl_open (&handle, devname.c_str(), 0) >= 0 && snd_ctl_card_info (handle, info) >= 0) { + + while (snd_ctl_pcm_next_device (handle, &device) >= 0 && device >= 0) { + + snd_pcm_info_set_device (pcminfo, device); + snd_pcm_info_set_subdevice (pcminfo, 0); + snd_pcm_info_set_stream (pcminfo, SND_PCM_STREAM_PLAYBACK); + + if (snd_ctl_pcm_info (handle, pcminfo) >= 0) { + devname += ','; + devname += PBD::to_string (device, std::dec); + devices.insert (std::make_pair (snd_pcm_info_get_name (pcminfo), devname)); + } + } + + snd_ctl_close(handle); + } + } +#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; + } +#endif +} + +void +ARDOUR::get_jack_portaudio_device_names (device_map_t& devices) +{ +#ifdef 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(); +#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_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 == 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); +} + +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 WIN32 + 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(), 1); +#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 WIN32 + gchar *install_dir = g_win32_get_package_installation_directory_of_module (NULL); + if (install_dir) { + sp.push_back (install_dir); + g_free (install_dir); + } + // don't try and use a system wide JACK install yet. +#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_matching_files_in_directories (server_dir_paths, Glib::PatternSpec(*i), server_paths); + } + 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; +} + +string +quote_string (const string& str) +{ + 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 (const JackCommandLineOptions& options, string& command_line) +{ + vector args; + + args.push_back (options.server_path); + +#ifdef WIN32 + // must use sync mode on windows + args.push_back ("-S"); + + // this needs to be added now on windows + if (!options.midi_driver.empty () && options.midi_driver != get_none_string ()) { + args.push_back ("-X"); + args.push_back (options.midi_driver); + } +#endif + + if (options.timeout) { + args.push_back ("-t"); + args.push_back (to_string (options.timeout, std::dec)); + } + + if (options.no_mlock) { + args.push_back ("-m"); + } + + args.push_back ("-p"); + args.push_back (to_string(options.ports_max, std::dec)); + + if (options.realtime) { + args.push_back ("-R"); + if (options.priority != 0) { + args.push_back ("-P"); + args.push_back (to_string(options.priority, std::dec)); + } + } else { + args.push_back ("-r"); + } + + if (options.unlock_gui_libs) { + args.push_back ("-u"); + } + + if (options.verbose) { + args.push_back ("-v"); + } + +#ifndef WIN32 + if (options.temporary) { + args.push_back ("-T"); + } +#endif + + string command_line_driver_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.output_device.empty() && options.input_device.empty()) { + return false; + } + + string command_line_input_device_name; + string command_line_output_device_name; + + 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 (get_jack_audio_driver_supports_setting_period_count (options.driver)) { + args.push_back ("-n"); + args.push_back (to_string (options.num_periods, std::dec)); + } + + args.push_back ("-r"); + args.push_back (to_string (options.samplerate, std::dec)); + + args.push_back ("-p"); + args.push_back (to_string (options.period_size, std::dec)); + + 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, std::dec)); + } + if (options.output_latency) { + args.push_back ("-0"); + args.push_back (to_string (options.output_latency, std::dec)); + } + } + + 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.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();) { +#ifdef WIN32 + oss << quote_string (*i); +#else + oss << *i; +#endif + 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) +{ + ofstream jackdrc (config_file_path.c_str()); + + if (!jackdrc) { + error << string_compose (_("cannot open JACK rc file %1 to store parameters"), config_file_path) << endmsg; + return false; + } + + jackdrc << command_line << endl; + jackdrc.close (); + return true; +} + +bool +ARDOUR::start_jack_server (const string& command_line) +{ +#ifdef WIN32 + STARTUPINFO si; + PROCESS_INFORMATION pi; + char * cmdline = g_strdup (command_line.c_str()); + + memset (&si, 0, sizeof (si)); + si.cb = sizeof (&si); + memset (&pi, 0, sizeof (pi)); + + if (!CreateProcess ( + NULL, // No module name, use command line + cmdline, + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // set handle inheritance to false + 0, // No creation flags + NULL, // Use parents environment block + NULL, // Use parents starting directory + &si, + &pi)) + { + error << string_compose ("cannot start JACK server: %s", g_win32_error_message (GetLastError ())) << endmsg; + } + + g_free (cmdline); + + // wait for 2 seconds for server to start + for (int i = 0; i < 8; ++i) { + Sleep (250); // 1/4 second + if (jack_server_running ()) return true; + } +#endif + return false; +} diff --git a/libs/ardour/test/jack_utils_test.cc b/libs/ardour/test/jack_utils_test.cc new file mode 100644 index 0000000000..ed80237f78 --- /dev/null +++ b/libs/ardour/test/jack_utils_test.cc @@ -0,0 +1,315 @@ + +#include + +#ifdef WIN32 +#include // only for Sleep +#endif + +#include + +#include "ardour/jack_utils.h" + +#include "jack_utils_test.h" + +CPPUNIT_TEST_SUITE_REGISTRATION (JackUtilsTest); + +using namespace std; +using namespace ARDOUR; + +void +JackUtilsTest::test_driver_names () +{ + vector driver_names; + + get_jack_audio_driver_names (driver_names); + + CPPUNIT_ASSERT(!driver_names.empty()); + + cout << endl; + cout << "Number of possible JACK Audio drivers found on this system: " << driver_names.size () << endl; + + for (vector::const_iterator i = driver_names.begin(); i != driver_names.end(); ++i) { + cout << "JACK Audio driver found: " << *i << endl; + } + + string default_audio_driver; + get_jack_default_audio_driver_name (default_audio_driver); + + cout << "The default audio driver on this system is: " << default_audio_driver << endl; + + driver_names.clear(); + + get_jack_midi_system_names (default_audio_driver, driver_names); + + CPPUNIT_ASSERT(!driver_names.empty()); + + cout << "Number of possible JACK MIDI drivers found on this system for default audio driver: " << driver_names.size () << endl; + + for (vector::const_iterator i = driver_names.begin(); i != driver_names.end(); ++i) { + cout << "JACK MIDI driver found: " << *i << endl; + } + + string default_midi_driver; + get_jack_default_midi_system_name (default_audio_driver, default_midi_driver); + + cout << "The default midi driver on this system is: " << default_midi_driver << endl; +} + +string +devices_string (const vector& devices) +{ + std::string str; + for (vector::const_iterator i = devices.begin(); i != devices.end();) { + str += *i; + if (++i != devices.end()) str += ", "; + } + return str; +} + +void +JackUtilsTest::test_device_names () +{ + vector driver_names; + + get_jack_audio_driver_names (driver_names); + + CPPUNIT_ASSERT(!driver_names.empty()); + + cout << endl; + + for (vector::const_iterator i = driver_names.begin(); i != driver_names.end(); ++i) { + string devices = devices_string (get_jack_device_names_for_audio_driver (*i)); + cout << "JACK Audio driver found: " << *i << " with devices: " << devices << endl; + } +} + +void +JackUtilsTest::test_samplerates () +{ + vector samplerates; + + get_jack_sample_rate_strings (samplerates); + cout << endl; + cout << "Number of possible Samplerates supported by JACK: " << samplerates.size () << endl; + + for (vector::const_iterator i = samplerates.begin(); i != samplerates.end(); ++i) { + cout << "Samplerate: " << *i << endl; + } +} + +void +JackUtilsTest::test_period_sizes () +{ + vector period_sizes; + + get_jack_period_size_strings (period_sizes); + cout << endl; + cout << "Number of possible Period sizes supported by JACK: " << period_sizes.size () << endl; + + for (vector::const_iterator i = period_sizes.begin(); i != period_sizes.end(); ++i) { + cout << "Period size: " << *i << endl; + } +} + +void +JackUtilsTest::test_dither_modes () +{ + vector driver_names; + + get_jack_audio_driver_names (driver_names); + + CPPUNIT_ASSERT(!driver_names.empty()); + + cout << endl; + + for (vector::const_iterator i = driver_names.begin(); i != driver_names.end(); ++i) { + vector dither_modes; + + get_jack_dither_mode_strings (*i, dither_modes); + cout << "Number of possible Dither Modes supported by JACK driver " << *i << + ": " << dither_modes.size () << endl; + for (vector::const_iterator j = dither_modes.begin(); j != dither_modes.end(); ++j) { + cout << "Dither Mode: " << *j << endl; + } + cout << endl; + } + +} + +void +JackUtilsTest::test_connect_server () +{ + cout << endl; + if (jack_server_running ()) { + cout << "Jack server running " << endl; + } else { + cout << "Jack server not running " << endl; + } +} + +void +JackUtilsTest::test_set_jack_path_env () +{ + cout << endl; + + bool path_env_set = false; + + string path_env = Glib::getenv ("PATH", path_env_set); + + if (path_env_set) { + cout << "PATH env set to: " << path_env << endl; + } else { + cout << "PATH env not set" << endl; + } + vector server_dirs; + get_jack_server_dir_paths (server_dirs); + set_path_env_for_jack_autostart (server_dirs); + + path_env_set = false; + + path_env = Glib::getenv ("PATH", path_env_set); + + CPPUNIT_ASSERT (path_env_set); + + cout << "After set_jack_path_env PATH env set to: " << path_env << endl; +} + +void +JackUtilsTest::test_server_paths () +{ + cout << endl; + + vector server_dirs; + + CPPUNIT_ASSERT (get_jack_server_dir_paths (server_dirs)); + + cout << "Number of Directories that may contain JACK servers: " << server_dirs.size () << endl; + + for (vector::const_iterator i = server_dirs.begin(); i != server_dirs.end(); ++i) { + cout << "JACK server directory path: " << *i << endl; + } + + vector server_names; + + CPPUNIT_ASSERT (get_jack_server_application_names (server_names)); + + cout << "Number of possible JACK server names on this system: " << server_names.size () << endl; + + for (vector::const_iterator i = server_names.begin(); i != server_names.end(); ++i) { + cout << "JACK server name: " << *i << endl; + } + + vector server_paths; + + CPPUNIT_ASSERT (get_jack_server_paths (server_dirs, server_names, server_paths)); + + cout << "Number of JACK servers on this system: " << server_paths.size () << endl; + + for (vector::const_iterator i = server_paths.begin(); i != server_paths.end(); ++i) { + cout << "JACK server path: " << *i << endl; + } + + vector server_paths2; + + CPPUNIT_ASSERT (get_jack_server_paths (server_paths2)); + + CPPUNIT_ASSERT (server_paths.size () == server_paths2.size ()); + + std::string default_server_path; + + CPPUNIT_ASSERT (get_jack_default_server_path (default_server_path)); + + cout << "The default JACK server on this system: " << default_server_path << endl; +} + +void +JackUtilsTest::test_config () +{ + +} + +void +JackUtilsTest::test_command_line () +{ + cout << endl; + + JackCommandLineOptions options; + + CPPUNIT_ASSERT (get_jack_default_server_path (options.server_path)); + + get_jack_default_audio_driver_name (options.driver); + + string command_line; + + // should fail, haven't set any device yet + CPPUNIT_ASSERT (!get_jack_command_line_string (options, command_line)); + + vector devices = get_jack_device_names_for_audio_driver (options.driver); + + if (!devices.empty()) { + options.input_device = devices.front (); + options.output_device = devices.front (); + } else { + cout << "No audio devices available using default JACK driver using Dummy driver" << endl; + options.driver = dummy_driver_name; + devices = get_jack_device_names_for_audio_driver (options.driver); + CPPUNIT_ASSERT (!devices.empty ()); + options.input_device = devices.front (); + options.output_device = devices.front (); + } + + options.input_device = devices.front (); + options.output_device = devices.front (); + + string midi_driver; + + get_jack_default_midi_system_name (options.driver, options.midi_driver); + + // this at least should create a valid jack command line + CPPUNIT_ASSERT (get_jack_command_line_string (options, command_line)); + + cout << "Default JACK command line: " << command_line << endl; +} + +void +JackUtilsTest::test_start_server () +{ +#ifdef WIN32 + cout << endl; + + JackCommandLineOptions options; + + CPPUNIT_ASSERT (get_jack_default_server_path (options.server_path)); + + cout << "Starting JACK server at path: " << options.server_path << endl; + + get_jack_default_audio_driver_name (options.driver); + + vector devices = get_jack_device_names_for_audio_driver (options.driver); + + if (!devices.empty()) { + options.input_device = devices.front (); + options.output_device = devices.front (); + } else { + cout << "No audio devices available using default JACK driver using Dummy driver" << endl; + options.driver = dummy_driver_name; + devices = get_jack_device_names_for_audio_driver (options.driver); + CPPUNIT_ASSERT (!devices.empty ()); + options.input_device = devices.front (); + options.output_device = devices.front (); + } + + string command_line; + // this at least should create a valid jack command line + CPPUNIT_ASSERT (get_jack_command_line_string (options, command_line)); + + cout << "Calling start_jack_server with command line: " << command_line << endl; + + CPPUNIT_ASSERT (start_jack_server (command_line)); + + // sleep for 10 seconds + Sleep (10*1000); + + CPPUNIT_ASSERT (jack_server_running ()); +#endif +} diff --git a/libs/ardour/test/jack_utils_test.h b/libs/ardour/test/jack_utils_test.h new file mode 100644 index 0000000000..6a42d1d015 --- /dev/null +++ b/libs/ardour/test/jack_utils_test.h @@ -0,0 +1,33 @@ + +#include +#include + +class JackUtilsTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE (JackUtilsTest); + CPPUNIT_TEST (test_driver_names); + CPPUNIT_TEST (test_device_names); + CPPUNIT_TEST (test_samplerates); + CPPUNIT_TEST (test_period_sizes); + CPPUNIT_TEST (test_dither_modes); + CPPUNIT_TEST (test_connect_server); + CPPUNIT_TEST (test_set_jack_path_env); + CPPUNIT_TEST (test_server_paths); + CPPUNIT_TEST (test_config); + CPPUNIT_TEST (test_command_line); + CPPUNIT_TEST (test_start_server); + CPPUNIT_TEST_SUITE_END (); + +public: + void test_driver_names (); + void test_device_names (); + void test_samplerates (); + void test_period_sizes (); + void test_dither_modes (); + void test_connect_server (); + void test_set_jack_path_env (); + void test_server_paths (); + void test_config (); + void test_command_line (); + void test_start_server (); +}; diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 256ff1c6c0..3afd4ce552 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -2,6 +2,7 @@ from waflib.extras import autowaf as autowaf from waflib import Options import os +import sys import re import subprocess @@ -99,6 +100,7 @@ libardour_sources = [ 'io.cc', 'io_processor.cc', 'jack_slave.cc', + 'jack_utils.cc', 'kmeterdsp.cc', 'ladspa_plugin.cc', 'ladspa_search_path.cc', @@ -240,6 +242,12 @@ def configure(conf): atleast_version='0.3.2') autowaf.check_pkg(conf, 'jack', uselib_store='JACK', atleast_version='0.118.2') + if Options.options.dist_target == 'auto': + if re.search ("linux", sys.platform) != None: + autowaf.check_pkg(conf, 'alsa', uselib_store='ALSA') + if Options.options.dist_target == 'mingw': + autowaf.check_pkg(conf, 'portaudio-2.0', uselib_store='PORTAUDIO', + atleast_version='19') autowaf.check_pkg(conf, 'libxml-2.0', uselib_store='XML') autowaf.check_pkg(conf, 'lrdf', uselib_store='LRDF', atleast_version='0.4.0') @@ -379,8 +387,8 @@ def build(bld): obj.name = 'ardour' obj.target = 'ardour' obj.uselib = ['GLIBMM','GTHREAD','AUBIO','SIGCPP','XML','UUID', - 'JACK','SNDFILE','SAMPLERATE','LRDF','AUDIOUNITS', - 'OSX','BOOST','CURL','DL'] + 'JACK', 'ALSA', 'PORTAUDIO', 'SNDFILE','SAMPLERATE','LRDF', + 'AUDIOUNITS', 'OSX','BOOST','CURL','DL'] obj.use = ['libpbd','libmidipp','libevoral','libvamphost', 'libvampplugin','libtaglib','librubberband', 'libaudiographer','libltc'] @@ -479,6 +487,7 @@ def build(bld): create_ardour_test_program(bld, obj.includes, 'framewalk_to_beats', 'test_framewalk_to_beats', ['test/framewalk_to_beats_test.cc']) create_ardour_test_program(bld, obj.includes, 'framepos_plus_beats', 'test_framepos_plus_beats', ['test/framepos_plus_beats_test.cc']) create_ardour_test_program(bld, obj.includes, 'framepos_minus_beats', 'test_framepos_minus_beats', ['test/framepos_minus_beats_test.cc']) + create_ardour_test_program(bld, obj.includes, 'jack_utils', 'test_jack_utils', ['test/jack_utils_test.cc']) create_ardour_test_program(bld, obj.includes, 'playlist_equivalent_regions', 'test_playlist_equivalent_regions', ['test/playlist_equivalent_regions_test.cc']) create_ardour_test_program(bld, obj.includes, 'playlist_layering', 'test_playlist_layering', ['test/playlist_layering_test.cc']) create_ardour_test_program(bld, obj.includes, 'plugins_test', 'test_plugins', ['test/plugins_test.cc']) @@ -497,6 +506,7 @@ def build(bld): test/framewalk_to_beats_test.cc test/framepos_plus_beats_test.cc test/framepos_minus_beats_test.cc + test/jack_utils_test.cc test/playlist_equivalent_regions_test.cc test/playlist_layering_test.cc test/plugins_test.cc