diff options
Diffstat (limited to 'libs/backends')
-rw-r--r-- | libs/backends/jack/jack_api.cc | 102 | ||||
-rw-r--r-- | libs/backends/jack/jack_audiobackend.cc | 1002 | ||||
-rw-r--r-- | libs/backends/jack/jack_audiobackend.h | 190 | ||||
-rw-r--r-- | libs/backends/jack/jack_connection.cc | 164 | ||||
-rw-r--r-- | libs/backends/jack/jack_connection.h | 40 | ||||
-rw-r--r-- | libs/backends/jack/jack_portengine.cc | 569 | ||||
-rw-r--r-- | libs/backends/jack/jack_portengine.h | 132 | ||||
-rw-r--r-- | libs/backends/jack/jack_utils.cc | 897 | ||||
-rw-r--r-- | libs/backends/jack/jack_utils.h | 235 | ||||
-rw-r--r-- | libs/backends/jack/wscript | 51 | ||||
-rw-r--r-- | libs/backends/wscript | 27 |
11 files changed, 3409 insertions, 0 deletions
diff --git a/libs/backends/jack/jack_api.cc b/libs/backends/jack/jack_api.cc new file mode 100644 index 0000000000..0136161181 --- /dev/null +++ b/libs/backends/jack/jack_api.cc @@ -0,0 +1,102 @@ +/* + Copyright (C) 2013 Paul Davis + + 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 "jack_connection.h" +#include "jack_audiobackend.h" +#include "jack_portengine.h" + +using namespace ARDOUR; + +static boost::shared_ptr<JACKAudioBackend> backend; +static boost::shared_ptr<JACKPortEngine> port_engine; +static boost::shared_ptr<JackConnection> jack_connection; + +static boost::shared_ptr<AudioBackend> +backend_factory (AudioEngine& ae) +{ + if (!jack_connection) { + return boost::shared_ptr<AudioBackend>(); + } + + if (!backend) { + backend.reset (new JACKAudioBackend (ae, jack_connection)); + } + + return backend; +} + +static boost::shared_ptr<PortEngine> +portengine_factory (PortManager& pm) +{ + if (!jack_connection) { + return boost::shared_ptr<PortEngine>(); + } + + if (!port_engine) { + port_engine.reset (new JACKPortEngine (pm, jack_connection)); + } + + return port_engine; +} + +static int +instantiate (const std::string& arg1, const std::string& arg2) +{ + try { + jack_connection.reset (new JackConnection (arg1, arg2)); + } catch (...) { + return -1; + } + + return 0; +} + +static int +deinstantiate () +{ + port_engine.reset (); + backend.reset (); + jack_connection.reset (); + + return 0; +} + +static bool +already_configured () +{ + return JackConnection::server_running (); +} + +extern "C" { + + + /* functions looked up using dlopen-and-cousins, and so naming scope + * must be non-mangled. + */ + + ARDOUR::AudioBackendInfo descriptor = { + "JACK", + instantiate, + deinstantiate, + backend_factory, + portengine_factory, + already_configured, + }; +} + diff --git a/libs/backends/jack/jack_audiobackend.cc b/libs/backends/jack/jack_audiobackend.cc new file mode 100644 index 0000000000..e004849896 --- /dev/null +++ b/libs/backends/jack/jack_audiobackend.cc @@ -0,0 +1,1002 @@ +/* + Copyright (C) 2013 Paul Davis + + 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 <string> +#include <list> +#include <math.h> + +#include <boost/scoped_ptr.hpp> +#include <glibmm/timer.h> +#include <glibmm/spawn.h> + +#include "pbd/error.h" + +#include "jack/jack.h" +#include "jack/thread.h" + +#include "ardour/audioengine.h" +#include "ardour/session.h" +#include "ardour/types.h" + +#include "jack_audiobackend.h" +#include "jack_connection.h" +#include "jack_portengine.h" +#include "jack_utils.h" + +#include "i18n.h" + +using namespace ARDOUR; +using namespace PBD; +using std::string; +using std::vector; +using std::cerr; +using std::endl; + +#define GET_PRIVATE_JACK_POINTER(localvar) jack_client_t* localvar = _jack_connection->jack(); if (!(localvar)) { return; } +#define GET_PRIVATE_JACK_POINTER_RET(localvar,r) jack_client_t* localvar = _jack_connection->jack(); if (!(localvar)) { return r; } + +JACKAudioBackend::JACKAudioBackend (AudioEngine& e, boost::shared_ptr<JackConnection> jc) + : AudioBackend (e) + , _jack_connection (jc) + , _running (false) + , _freewheeling (false) + , _target_sample_rate (48000) + , _target_buffer_size (1024) + , _target_sample_format (FormatFloat) + , _target_interleaved (false) + , _target_input_channels (-1) + , _target_output_channels (-1) + , _target_systemic_input_latency (0) + , _target_systemic_output_latency (0) + , _current_sample_rate (0) + , _current_buffer_size (0) +{ + _jack_connection->Disconnected.connect_same_thread (disconnect_connection, boost::bind (&JACKAudioBackend::disconnected, this, _1)); +} + +JACKAudioBackend::~JACKAudioBackend() +{ +} + +string +JACKAudioBackend::name() const +{ + return X_("JACK"); +} + +void* +JACKAudioBackend::private_handle() const +{ + return _jack_connection->jack(); +} + +bool +JACKAudioBackend::connected() const +{ + return (private_handle() != 0); +} + +bool +JACKAudioBackend::is_realtime () const +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack,false); + return jack_is_realtime (_priv_jack); +} + +bool +JACKAudioBackend::requires_driver_selection() const +{ + return true; +} + +vector<string> +JACKAudioBackend::enumerate_drivers () const +{ + vector<string> currently_available; + get_jack_audio_driver_names (currently_available); + return currently_available; +} + +int +JACKAudioBackend::set_driver (const std::string& name) +{ + _target_driver = name; + return 0; +} + +vector<AudioBackend::DeviceStatus> +JACKAudioBackend::enumerate_devices () const +{ + vector<string> currently_available = get_jack_device_names_for_audio_driver (_target_driver); + vector<DeviceStatus> statuses; + + if (all_devices.find (_target_driver) == all_devices.end()) { + all_devices.insert (make_pair (_target_driver, std::set<string>())); + } + + /* store every device we've found, by driver name. + * + * This is so we do not confuse ALSA, FFADO, netjack etc. devices + * with each other. + */ + + DeviceList& all (all_devices[_target_driver]); + + for (vector<string>::const_iterator d = currently_available.begin(); d != currently_available.end(); ++d) { + all.insert (*d); + } + + for (DeviceList::const_iterator d = all.begin(); d != all.end(); ++d) { + if (find (currently_available.begin(), currently_available.end(), *d) == currently_available.end()) { + statuses.push_back (DeviceStatus (*d, false)); + } else { + statuses.push_back (DeviceStatus (*d, false)); + } + } + + return statuses; +} + +vector<float> +JACKAudioBackend::available_sample_rates (const string& /*device*/) const +{ + vector<float> f; + + if (connected()) { + f.push_back (sample_rate()); + return f; + } + + /* if JACK is not already running, just list a bunch of reasonable + values and let the future sort it all out. + */ + + f.push_back (8000.0); + f.push_back (16000.0); + f.push_back (24000.0); + f.push_back (32000.0); + f.push_back (44100.0); + f.push_back (48000.0); + f.push_back (88200.0); + f.push_back (96000.0); + f.push_back (192000.0); + f.push_back (384000.0); + + return f; +} + +vector<uint32_t> +JACKAudioBackend::available_buffer_sizes (const string& /*device*/) const +{ + vector<uint32_t> s; + + if (connected()) { + s.push_back (buffer_size()); + return s; + } + + s.push_back (8); + s.push_back (16); + s.push_back (32); + s.push_back (64); + s.push_back (128); + s.push_back (256); + s.push_back (512); + s.push_back (1024); + s.push_back (2048); + s.push_back (4096); + s.push_back (8192); + + return s; +} + +uint32_t +JACKAudioBackend::available_input_channel_count (const string& /*device*/) const +{ + return 128; +} + +uint32_t +JACKAudioBackend::available_output_channel_count (const string& /*device*/) const +{ + return 128; +} + +/* -- parameter setting -- */ + +int +JACKAudioBackend::set_device_name (const string& dev) +{ + if (connected()) { + /* need to stop and restart JACK for this to work, at present */ + return -1; + } + + _target_device = dev; + return 0; +} + +int +JACKAudioBackend::set_sample_rate (float sr) +{ + if (!connected()) { + _target_sample_rate = sr; + return 0; + } + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + + if (sr == jack_get_sample_rate (_priv_jack)) { + return 0; + } + + return -1; +} + +int +JACKAudioBackend::set_buffer_size (uint32_t nframes) +{ + if (!connected()) { + _target_buffer_size = nframes; + return 0; + } + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + + if (nframes == jack_get_buffer_size (_priv_jack)) { + return 0; + } + + return jack_set_buffer_size (_priv_jack, nframes); +} + +int +JACKAudioBackend::set_sample_format (SampleFormat sf) +{ + /* as far as JACK clients are concerned, the hardware is always + * floating point format. + */ + if (sf == FormatFloat) { + return 0; + } + return -1; +} + +int +JACKAudioBackend::set_interleaved (bool yn) +{ + /* as far as JACK clients are concerned, the hardware is always + * non-interleaved + */ + if (!yn) { + return 0; + } + return -1; +} + +int +JACKAudioBackend::set_input_channels (uint32_t cnt) +{ + if (connected()) { + return -1; + } + + _target_input_channels = cnt; + + return 0; +} + +int +JACKAudioBackend::set_output_channels (uint32_t cnt) +{ + if (connected()) { + return -1; + } + + _target_output_channels = cnt; + + return 0; +} + +int +JACKAudioBackend::set_systemic_input_latency (uint32_t l) +{ + if (connected()) { + return -1; + } + + _target_systemic_input_latency = l; + + return 0; +} + +int +JACKAudioBackend::set_systemic_output_latency (uint32_t l) +{ + if (connected()) { + return -1; + } + + _target_systemic_output_latency = l; + + return 0; +} + +/* --- Parameter retrieval --- */ + +std::string +JACKAudioBackend::device_name () const +{ + if (connected()) { + return "???"; + } + + return _target_device; +} + +float +JACKAudioBackend::sample_rate () const +{ + if (connected()) { + return _current_sample_rate; + } + return _target_sample_rate; +} + +uint32_t +JACKAudioBackend::buffer_size () const +{ + if (connected()) { + return _current_buffer_size; + } + return _target_buffer_size; +} + +SampleFormat +JACKAudioBackend::sample_format () const +{ + return FormatFloat; +} + +bool +JACKAudioBackend::interleaved () const +{ + return false; +} + +uint32_t +JACKAudioBackend::input_channels () const +{ + if (connected()) { + return n_physical (JackPortIsInput).n_audio(); + } + return _target_input_channels; +} + +uint32_t +JACKAudioBackend::output_channels () const +{ + if (connected()) { + return n_physical (JackPortIsOutput).n_audio(); + } + return _target_output_channels; +} + +uint32_t +JACKAudioBackend::systemic_input_latency () const +{ + return _target_systemic_output_latency; +} + +uint32_t +JACKAudioBackend::systemic_output_latency () const +{ + return _target_systemic_output_latency; +} + +size_t +JACKAudioBackend::raw_buffer_size(DataType t) +{ + std::map<DataType,size_t>::const_iterator s = _raw_buffer_sizes.find(t); + return (s != _raw_buffer_sizes.end()) ? s->second : 0; +} + +void +JACKAudioBackend::setup_jack_startup_command () +{ + /* first we map the parameters that have been set onto a + * JackCommandLineOptions object. + */ + + JackCommandLineOptions options; + + get_jack_default_server_path (options.server_path); + options.driver = _target_driver; + options.samplerate = _target_sample_rate; + options.period_size = _target_buffer_size; + options.num_periods = 2; + options.input_device = _target_device; + options.output_device = _target_device; + options.input_latency = _target_systemic_input_latency; + options.output_latency = _target_systemic_output_latency; + options.input_channels = _target_input_channels; + options.output_channels = _target_output_channels; + if (_target_sample_format == FormatInt16) { + options.force16_bit = _target_sample_format; + } + options.realtime = true; + options.ports_max = 2048; + + /* this must always be true for any server instance we start ourselves + */ + + options.temporary = true; + + string cmdline; + + if (!get_jack_command_line_string (options, cmdline)) { + /* error, somehow */ + return; + } + + std::cerr << "JACK command line will be: " << cmdline << std::endl; + + write_jack_config_file (get_jack_server_user_config_file_path(), cmdline); +} + +/* ---- BASIC STATE CONTROL API: start/stop/pause/freewheel --- */ + +int +JACKAudioBackend::start () +{ + if (!connected()) { + + if (!_jack_connection->server_running()) { + setup_jack_startup_command (); + } + + if (_jack_connection->open ()) { + return -1; + } + } + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + + /* get the buffer size and sample rates established */ + + jack_sample_rate_callback (jack_get_sample_rate (_priv_jack)); + jack_bufsize_callback (jack_get_buffer_size (_priv_jack)); + + /* Now that we have buffer size and sample rate established, the engine + can go ahead and do its stuff + */ + + engine.reestablish_ports (); + + if (!jack_port_type_get_buffer_size) { + warning << _("This version of JACK is old - you should upgrade to a newer version that supports jack_port_type_get_buffer_size()") << endmsg; + } + + set_jack_callbacks (); + + if (jack_activate (_priv_jack) == 0) { + _running = true; + } else { + // error << _("cannot activate JACK client") << endmsg; + } + + engine.reconnect_ports (); + + return 0; +} + +int +JACKAudioBackend::stop () +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + + _jack_connection->close (); + + _current_buffer_size = 0; + _current_sample_rate = 0; + + _raw_buffer_sizes.clear(); + + return 0; +} + +int +JACKAudioBackend::pause () +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + + if (_priv_jack) { + jack_deactivate (_priv_jack); + } + + return 0; +} + +int +JACKAudioBackend::freewheel (bool onoff) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + + if (onoff == _freewheeling) { + /* already doing what has been asked for */ + + return 0; + } + + if (jack_set_freewheel (_priv_jack, onoff) == 0) { + _freewheeling = true; + return 0; + } + + return -1; +} + +/* --- TRANSPORT STATE MANAGEMENT --- */ + +void +JACKAudioBackend::transport_stop () +{ + GET_PRIVATE_JACK_POINTER (_priv_jack); + jack_transport_stop (_priv_jack); +} + +void +JACKAudioBackend::transport_start () +{ + GET_PRIVATE_JACK_POINTER (_priv_jack); + jack_transport_start (_priv_jack); +} + +void +JACKAudioBackend::transport_locate (framepos_t where) +{ + GET_PRIVATE_JACK_POINTER (_priv_jack); + jack_transport_locate (_priv_jack, where); +} + +framepos_t +JACKAudioBackend::transport_frame () const +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + return jack_get_current_transport_frame (_priv_jack); +} + +TransportState +JACKAudioBackend::transport_state () const +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, ((TransportState) JackTransportStopped)); + jack_position_t pos; + return (TransportState) jack_transport_query (_priv_jack, &pos); +} + +int +JACKAudioBackend::set_time_master (bool yn) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + if (yn) { + return jack_set_timebase_callback (_priv_jack, 0, _jack_timebase_callback, this); + } else { + return jack_release_timebase (_priv_jack); + } +} + +/* process-time */ + +bool +JACKAudioBackend::get_sync_offset (pframes_t& offset) const +{ + +#ifdef HAVE_JACK_VIDEO_SUPPORT + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, false); + + jack_position_t pos; + + if (_priv_jack) { + (void) jack_transport_query (_priv_jack, &pos); + + if (pos.valid & JackVideoFrameOffset) { + offset = pos.video_offset; + return true; + } + } +#else + /* keep gcc happy */ + offset = 0; +#endif + + return false; +} + +pframes_t +JACKAudioBackend::sample_time () +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + return jack_frame_time (_priv_jack); +} + +pframes_t +JACKAudioBackend::sample_time_at_cycle_start () +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + return jack_last_frame_time (_priv_jack); +} + +pframes_t +JACKAudioBackend::samples_since_cycle_start () +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + return jack_frames_since_cycle_start (_priv_jack); +} + +/* JACK Callbacks */ + +static void +ardour_jack_error (const char* msg) +{ + error << "JACK: " << msg << endmsg; +} + +void +JACKAudioBackend::set_jack_callbacks () +{ + GET_PRIVATE_JACK_POINTER (_priv_jack); + + jack_set_thread_init_callback (_priv_jack, AudioEngine::thread_init_callback, 0); + + jack_set_process_thread (_priv_jack, _process_thread, this); + jack_set_sample_rate_callback (_priv_jack, _sample_rate_callback, this); + jack_set_buffer_size_callback (_priv_jack, _bufsize_callback, this); + jack_set_xrun_callback (_priv_jack, _xrun_callback, this); + jack_set_sync_callback (_priv_jack, _jack_sync_callback, this); + jack_set_freewheel_callback (_priv_jack, _freewheel_callback, this); + +#ifdef HAVE_JACK_SESSION + if( jack_set_session_callback) + jack_set_session_callback (_priv_jack, _session_callback, this); +#endif + + if (jack_set_latency_callback) { + jack_set_latency_callback (_priv_jack, _latency_callback, this); + } + + jack_set_error_function (ardour_jack_error); +} + +void +JACKAudioBackend::_jack_timebase_callback (jack_transport_state_t state, pframes_t nframes, + jack_position_t* pos, int new_position, void *arg) +{ + static_cast<JACKAudioBackend*> (arg)->jack_timebase_callback (state, nframes, pos, new_position); +} + +void +JACKAudioBackend::jack_timebase_callback (jack_transport_state_t state, pframes_t nframes, + jack_position_t* pos, int new_position) +{ + ARDOUR::Session* session = engine.session(); + + if (session) { + session->jack_timebase_callback (state, nframes, pos, new_position); + } +} + +int +JACKAudioBackend::_jack_sync_callback (jack_transport_state_t state, jack_position_t* pos, void* arg) +{ + return static_cast<JACKAudioBackend*> (arg)->jack_sync_callback (state, pos); +} + +int +JACKAudioBackend::jack_sync_callback (jack_transport_state_t state, jack_position_t* pos) +{ + TransportState tstate; + + switch (state) { + case JackTransportStopped: + tstate = TransportStopped; + break; + case JackTransportRolling: + tstate = TransportRolling; + break; + case JackTransportLooping: + tstate = TransportLooping; + break; + case JackTransportStarting: + tstate = TransportStarting; + break; + } + + return engine.sync_callback (tstate, pos->frame); + + return true; +} + +int +JACKAudioBackend::_xrun_callback (void *arg) +{ + JACKAudioBackend* ae = static_cast<JACKAudioBackend*> (arg); + if (ae->connected()) { + ae->engine.Xrun (); /* EMIT SIGNAL */ + } + return 0; +} + +#ifdef HAVE_JACK_SESSION +void +JACKAudioBackend::_session_callback (jack_session_event_t *event, void *arg) +{ + JACKAudioBackend* ae = static_cast<JACKAudioBackend*> (arg); + ARDOUR::Session* session = ae->engine.session(); + + if (session) { + session->jack_session_event (event); + } +} +#endif + +void +JACKAudioBackend::_freewheel_callback (int onoff, void *arg) +{ + static_cast<JACKAudioBackend*>(arg)->freewheel_callback (onoff); +} + +void +JACKAudioBackend::freewheel_callback (int onoff) +{ + _freewheeling = onoff; + engine.freewheel_callback (onoff); +} + +void +JACKAudioBackend::_latency_callback (jack_latency_callback_mode_t mode, void* arg) +{ + return static_cast<JACKAudioBackend*> (arg)->jack_latency_callback (mode); +} + +int +JACKAudioBackend::create_process_thread (boost::function<void()> f, pthread_t* thread, size_t stacksize) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + ThreadData* td = new ThreadData (this, f, stacksize); + + if (jack_client_create_thread (_priv_jack, thread, jack_client_real_time_priority (_priv_jack), + jack_is_realtime (_priv_jack), _start_process_thread, td)) { + return -1; + } + + return 0; +} + +void* +JACKAudioBackend::_start_process_thread (void* arg) +{ + ThreadData* td = reinterpret_cast<ThreadData*> (arg); + boost::function<void()> f = td->f; + delete td; + + f (); + + return 0; +} + +void* +JACKAudioBackend::_process_thread (void *arg) +{ + return static_cast<JACKAudioBackend*> (arg)->process_thread (); +} + +void* +JACKAudioBackend::process_thread () +{ + /* JACK doesn't do this for us when we use the wait API + */ + + AudioEngine::thread_init_callback (this); + + while (1) { + GET_PRIVATE_JACK_POINTER_RET(_priv_jack,0); + + pframes_t nframes = jack_cycle_wait (_priv_jack); + + if (engine.process_callback (nframes)) { + return 0; + } + + jack_cycle_signal (_priv_jack, 0); + } + + return 0; +} + +int +JACKAudioBackend::_sample_rate_callback (pframes_t nframes, void *arg) +{ + return static_cast<JACKAudioBackend*> (arg)->jack_sample_rate_callback (nframes); +} + +int +JACKAudioBackend::jack_sample_rate_callback (pframes_t nframes) +{ + _current_sample_rate = nframes; + return engine.sample_rate_change (nframes); +} + +void +JACKAudioBackend::jack_latency_callback (jack_latency_callback_mode_t mode) +{ + engine.latency_callback (mode == JackPlaybackLatency); +} + +int +JACKAudioBackend::_bufsize_callback (pframes_t nframes, void *arg) +{ + return static_cast<JACKAudioBackend*> (arg)->jack_bufsize_callback (nframes); +} + +int +JACKAudioBackend::jack_bufsize_callback (pframes_t nframes) +{ + /* if the size has not changed, this should be a no-op */ + + if (nframes == _current_buffer_size) { + return 0; + } + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 1); + + _current_buffer_size = nframes; + + if (jack_port_type_get_buffer_size) { + _raw_buffer_sizes[DataType::AUDIO] = jack_port_type_get_buffer_size (_priv_jack, JACK_DEFAULT_AUDIO_TYPE); + _raw_buffer_sizes[DataType::MIDI] = jack_port_type_get_buffer_size (_priv_jack, JACK_DEFAULT_MIDI_TYPE); + } else { + + /* Old version of JACK. + + These crude guesses, see below where we try to get the right answers. + + Note that our guess for MIDI deliberatey tries to overestimate + by a little. It would be nicer if we could get the actual + size from a port, but we have to use this estimate in the + event that there are no MIDI ports currently. If there are + the value will be adjusted below. + */ + + _raw_buffer_sizes[DataType::AUDIO] = nframes * sizeof (Sample); + _raw_buffer_sizes[DataType::MIDI] = nframes * 4 - (nframes/2); + } + + engine.buffer_size_change (nframes); + + return 0; +} + +void +JACKAudioBackend::disconnected (const char* why) +{ + bool was_running = _running; + + _running = false; + _current_buffer_size = 0; + _current_sample_rate = 0; + + if (was_running) { + engine.halted_callback (why); /* EMIT SIGNAL */ + } +} +float +JACKAudioBackend::cpu_load() const +{ + GET_PRIVATE_JACK_POINTER_RET(_priv_jack,0); + return jack_cpu_load (_priv_jack); +} + +void +JACKAudioBackend::update_latencies () +{ + GET_PRIVATE_JACK_POINTER (_priv_jack); + jack_recompute_total_latencies (_priv_jack); +} + +ChanCount +JACKAudioBackend::n_physical (unsigned long flags) const +{ + ChanCount c; + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, c); + + const char ** ports = jack_get_ports (_priv_jack, NULL, NULL, JackPortIsPhysical | flags); + + if (ports) { + for (uint32_t i = 0; ports[i]; ++i) { + if (!strstr (ports[i], "Midi-Through")) { + DataType t (jack_port_type (jack_port_by_name (_priv_jack, ports[i]))); + c.set (t, c.get (t) + 1); + } + } + + jack_free (ports); + } + + return c; +} + +bool +JACKAudioBackend::can_change_sample_rate_when_running () const +{ + return false; +} + +bool +JACKAudioBackend::can_change_buffer_size_when_running () const +{ + return true; +} + +string +JACKAudioBackend::control_app_name () const +{ + /* Since JACK/ALSA really don't provide particularly integrated support + for the idea of a control app to be used to control a device, + allow the user to take some control themselves if necessary. + */ + + const char* env_value = g_getenv ("ARDOUR_DEVICE_CONTROL_APP"); + string appname; + + if (!env_value) { + if (_target_driver.empty() || _target_device.empty()) { + return appname; + } + + if (_target_driver == "ALSA") { + + if (_target_device == "Hammerfall DSP") { + appname = "hdspconf"; + } else if (_target_device == "M Audio Delta 1010") { + appname = "mudita24"; + } + } + } else { + appname = env_value; + } + + return appname; +} + +void +JACKAudioBackend::launch_control_app () +{ + string appname = control_app_name(); + + if (appname.empty()) { + error << string_compose (_("There is no control application for the device \"%1\""), _target_device) << endmsg; + return; + } + + std::list<string> args; + args.push_back (appname); + Glib::spawn_async ("", args, Glib::SPAWN_SEARCH_PATH); +} diff --git a/libs/backends/jack/jack_audiobackend.h b/libs/backends/jack/jack_audiobackend.h new file mode 100644 index 0000000000..1389e15c4a --- /dev/null +++ b/libs/backends/jack/jack_audiobackend.h @@ -0,0 +1,190 @@ +/* + Copyright (C) 2013 Paul Davis + + 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. + +*/ + +#ifndef __libardour_jack_audiobackend_h__ +#define __libardour_jack_audiobackend_h__ + +#include <string> +#include <vector> +#include <map> +#include <set> + +#include <stdint.h> + +#include <boost/shared_ptr.hpp> + +#include <jack/jack.h> +#ifdef HAVE_JACK_SESSION +#include <jack/session.h> +#endif + +#include "ardour/audio_backend.h" + +namespace ARDOUR { + +class JackConnection; + +class JACKAudioBackend : public AudioBackend { + public: + JACKAudioBackend (AudioEngine& e, boost::shared_ptr<JackConnection>); + ~JACKAudioBackend (); + + std::string name() const; + void* private_handle() const; + bool connected() const; + bool is_realtime () const; + + bool requires_driver_selection() const; + std::vector<std::string> enumerate_drivers () const; + int set_driver (const std::string&); + + std::vector<DeviceStatus> enumerate_devices () const; + + std::vector<float> available_sample_rates (const std::string& device) const; + std::vector<uint32_t> available_buffer_sizes (const std::string& device) const; + uint32_t available_input_channel_count (const std::string& device) const; + uint32_t available_output_channel_count (const std::string& device) const; + + bool can_change_sample_rate_when_running() const; + bool can_change_buffer_size_when_running() const; + + int set_device_name (const std::string&); + int set_sample_rate (float); + int set_buffer_size (uint32_t); + int set_sample_format (SampleFormat); + int set_interleaved (bool yn); + int set_input_channels (uint32_t); + int set_output_channels (uint32_t); + int set_systemic_input_latency (uint32_t); + int set_systemic_output_latency (uint32_t); + + std::string device_name () const; + float sample_rate () const; + uint32_t buffer_size () const; + SampleFormat sample_format () const; + bool interleaved () const; + uint32_t input_channels () const; + uint32_t output_channels () const; + uint32_t systemic_input_latency () const; + uint32_t systemic_output_latency () const; + + std::string control_app_name () const; + void launch_control_app (); + + int start (); + int stop (); + int pause (); + int freewheel (bool); + + float cpu_load() const; + + pframes_t sample_time (); + pframes_t sample_time_at_cycle_start (); + pframes_t samples_since_cycle_start (); + + size_t raw_buffer_size (DataType t); + + int create_process_thread (boost::function<void()> func, pthread_t*, size_t stacksize); + + void transport_start (); + void transport_stop (); + void transport_locate (framepos_t /*pos*/); + TransportState transport_state () const; + framepos_t transport_frame() const; + + int set_time_master (bool /*yn*/); + bool get_sync_offset (pframes_t& /*offset*/) const; + + void update_latencies (); + + static bool already_configured(); + + private: + boost::shared_ptr<JackConnection> _jack_connection; //< shared with JACKPortEngine + bool _running; + bool _freewheeling; + std::map<DataType,size_t> _raw_buffer_sizes; + + static int _xrun_callback (void *arg); + static void* _process_thread (void *arg); + static int _sample_rate_callback (pframes_t nframes, void *arg); + static int _bufsize_callback (pframes_t nframes, void *arg); + static void _jack_timebase_callback (jack_transport_state_t, pframes_t, jack_position_t*, int, void*); + static int _jack_sync_callback (jack_transport_state_t, jack_position_t*, void *arg); + static void _freewheel_callback (int , void *arg); + static void _latency_callback (jack_latency_callback_mode_t, void*); +#ifdef HAVE_JACK_SESSION + static void _session_callback (jack_session_event_t *event, void *arg); +#endif + + void jack_timebase_callback (jack_transport_state_t, pframes_t, jack_position_t*, int); + int jack_sync_callback (jack_transport_state_t, jack_position_t*); + int jack_bufsize_callback (pframes_t); + int jack_sample_rate_callback (pframes_t); + void freewheel_callback (int); + int process_callback (pframes_t nframes); + void jack_latency_callback (jack_latency_callback_mode_t); + void disconnected (const char*); + + void set_jack_callbacks (); + int reconnect_to_jack (); + + struct ThreadData { + JACKAudioBackend* engine; + boost::function<void()> f; + size_t stacksize; + + ThreadData (JACKAudioBackend* e, boost::function<void()> fp, size_t stacksz) + : engine (e) , f (fp) , stacksize (stacksz) {} + }; + + void* process_thread (); + static void* _start_process_thread (void*); + + ChanCount n_physical (unsigned long) const; + + void setup_jack_startup_command (); + + /* pffooo */ + + std::string _target_driver; + std::string _target_device; + float _target_sample_rate; + uint32_t _target_buffer_size; + SampleFormat _target_sample_format; + bool _target_interleaved; + uint32_t _target_input_channels; + uint32_t _target_output_channels; + uint32_t _target_systemic_input_latency; + uint32_t _target_systemic_output_latency; + uint32_t _current_sample_rate; + uint32_t _current_buffer_size; + + typedef std::set<std::string> DeviceList; + typedef std::map<std::string,DeviceList> DriverDeviceMap; + + mutable DriverDeviceMap all_devices; + + PBD::ScopedConnection disconnect_connection; +}; + +} // namespace + +#endif /* __ardour_audiobackend_h__ */ + diff --git a/libs/backends/jack/jack_connection.cc b/libs/backends/jack/jack_connection.cc new file mode 100644 index 0000000000..da0127c584 --- /dev/null +++ b/libs/backends/jack/jack_connection.cc @@ -0,0 +1,164 @@ +/* + Copyright (C) 2013 Paul Davis + + 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 <boost/scoped_ptr.hpp> +#include <jack/session.h> + +#include "pbd/epa.h" + +#include "jack_connection.h" +#include "jack_utils.h" + +#define GET_PRIVATE_JACK_POINTER(j) jack_client_t* _priv_jack = (jack_client_t*) (j); if (!_priv_jack) { return; } +#define GET_PRIVATE_JACK_POINTER_RET(j,r) jack_client_t* _priv_jack = (jack_client_t*) (j); if (!_priv_jack) { return r; } + +using namespace ARDOUR; +using namespace PBD; +using std::string; +using std::vector; + +static void jack_halted_callback (void* arg) +{ + JackConnection* jc = static_cast<JackConnection*> (arg); + jc->halted_callback (); +} + +static void jack_halted_info_callback (jack_status_t code, const char* reason, void* arg) +{ + JackConnection* jc = static_cast<JackConnection*> (arg); + jc->halted_info_callback (code, reason); +} + + +JackConnection::JackConnection (const std::string& arg1, const std::string& arg2) + : _jack (0) + , _client_name (arg1) + , session_uuid (arg2) +{ +} + +JackConnection::~JackConnection () +{ + close (); +} + +bool +JackConnection::server_running () +{ + EnvironmentalProtectionAgency* global_epa = EnvironmentalProtectionAgency::get_global_epa (); + boost::scoped_ptr<EnvironmentalProtectionAgency> current_epa; + + /* revert all environment settings back to whatever they were when + * ardour started, because ardour's startup script may have reset + * something in ways that interfere with finding/starting JACK. + */ + + 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; +} + +int +JackConnection::open () +{ + EnvironmentalProtectionAgency* global_epa = EnvironmentalProtectionAgency::get_global_epa (); + boost::scoped_ptr<EnvironmentalProtectionAgency> current_epa; + jack_status_t status; + + close (); + + /* 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 (); + } + + /* ensure that PATH or equivalent includes likely locations of the JACK + * server, in case the user's default does not. + */ + + vector<string> dirs; + get_jack_server_dir_paths (dirs); + set_path_env_for_jack_autostart (dirs); + + if ((_jack = jack_client_open (_client_name.c_str(), JackSessionID, &status, session_uuid.c_str())) == 0) { + return -1; + } + + if (status & JackNameNotUnique) { + _client_name = jack_get_client_name (_jack); + } + + /* attach halted handler */ + + if (jack_on_info_shutdown) { + jack_on_info_shutdown (_jack, jack_halted_info_callback, this); + } else { + jack_on_shutdown (_jack, jack_halted_callback, this); + } + + + Connected(); /* EMIT SIGNAL */ + + return 0; +} + +int +JackConnection::close () +{ + GET_PRIVATE_JACK_POINTER_RET (_jack, -1); + + if (_priv_jack) { + int ret = jack_client_close (_priv_jack); + _jack = 0; + Disconnected (""); /* EMIT SIGNAL */ + return ret; + } + + return 0; +} + +void +JackConnection::halted_callback () +{ + _jack = 0; + Disconnected (""); +} + +void +JackConnection::halted_info_callback (jack_status_t /*status*/, const char* reason) +{ + _jack = 0; + Disconnected (reason); +} + + diff --git a/libs/backends/jack/jack_connection.h b/libs/backends/jack/jack_connection.h new file mode 100644 index 0000000000..cd45f3b9ba --- /dev/null +++ b/libs/backends/jack/jack_connection.h @@ -0,0 +1,40 @@ +#ifndef __libardour_jack_connection_h__ +#define __libardour_jack_connection_h__ + +#include <string> +#include <jack/jack.h> + +#include "pbd/signals.h" + +namespace ARDOUR { + +class JackConnection { + public: + JackConnection (const std::string& client_name, const std::string& session_uuid); + ~JackConnection (); + + const std::string& client_name() const { return _client_name; } + + int open (); + int close (); + bool connected () const { return _jack != 0; } + + jack_client_t* jack() const { return _jack; } + + PBD::Signal0<void> Connected; + PBD::Signal1<void,const char*> Disconnected; + + void halted_callback (); + void halted_info_callback (jack_status_t, const char*); + + static bool server_running(); + + private: + jack_client_t* volatile _jack; + std::string _client_name; + std::string session_uuid; +}; + +} // namespace + +#endif /* __libardour_jack_connection_h__ */ diff --git a/libs/backends/jack/jack_portengine.cc b/libs/backends/jack/jack_portengine.cc new file mode 100644 index 0000000000..bd352a2f11 --- /dev/null +++ b/libs/backends/jack/jack_portengine.cc @@ -0,0 +1,569 @@ +/* + Copyright (C) 2013 Paul Davis + + 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 <string.h> +#include <stdint.h> + +#include "pbd/error.h" + +#include "jack_portengine.h" +#include "jack_connection.h" + +#include "ardour/port_manager.h" + +#include "i18n.h" + +using namespace ARDOUR; +using namespace PBD; +using std::string; +using std::vector; + +#define GET_PRIVATE_JACK_POINTER(localvar) jack_client_t* localvar = _jack_connection->jack(); if (!(localvar)) { return; } +#define GET_PRIVATE_JACK_POINTER_RET(localvar,r) jack_client_t* localvar = _jack_connection->jack(); if (!(localvar)) { return r; } + +static uint32_t +ardour_port_flags_to_jack_flags (PortFlags flags) +{ + uint32_t jack_flags = 0; + + if (flags & IsInput) { + jack_flags |= JackPortIsInput; + } + if (flags & IsOutput) { + jack_flags |= JackPortIsOutput; + } + if (flags & IsTerminal) { + jack_flags |= JackPortIsTerminal; + } + if (flags & IsPhysical) { + jack_flags |= JackPortIsPhysical; + } + if (flags & CanMonitor) { + jack_flags |= JackPortCanMonitor; + } + + return jack_flags; +} + +static DataType +jack_port_type_to_ardour_data_type (const char* jack_type) +{ + if (strcmp (jack_type, JACK_DEFAULT_AUDIO_TYPE) == 0) { + return DataType::AUDIO; + } else if (strcmp (jack_type, JACK_DEFAULT_MIDI_TYPE) == 0) { + return DataType::MIDI; + } + return DataType::NIL; +} + +static const char* +ardour_data_type_to_jack_port_type (DataType d) +{ + switch (d) { + case DataType::AUDIO: + return JACK_DEFAULT_AUDIO_TYPE; + case DataType::MIDI: + return JACK_DEFAULT_MIDI_TYPE; + } + + return ""; +} + +JACKPortEngine::JACKPortEngine (PortManager& pm, boost::shared_ptr<JackConnection> jc) + : PortEngine (pm) + , _jack_connection (jc) +{ + _jack_connection->Connected.connect_same_thread (jack_connection_connection, boost::bind (&JACKPortEngine::connected_to_jack, this)); +} + +JACKPortEngine::~JACKPortEngine () +{ + /* a default destructor would do this, and so would this one, + but we'll make it explicit in case we ever need to debug + the lifetime of the JACKConnection + */ + _jack_connection.reset (); +} + +void +JACKPortEngine::connected_to_jack () +{ + /* register callbacks for stuff that is our responsibility */ + + jack_client_t* client = _jack_connection->jack(); + + if (!client) { + /* how could this happen? it could ... */ + error << _("Already disconnected from JACK before PortEngine could register callbacks") << endmsg; + return; + } + + jack_set_port_registration_callback (client, _registration_callback, this); + jack_set_port_connect_callback (client, _connect_callback, this); + jack_set_graph_order_callback (client, _graph_order_callback, this); +} + +void* +JACKPortEngine::private_handle() const +{ + return _jack_connection->jack(); +} + +bool +JACKPortEngine::connected() const +{ + return _jack_connection->connected(); +} + +int +JACKPortEngine::set_port_name (PortHandle port, const std::string& name) +{ + return jack_port_set_name ((jack_port_t*) port, name.c_str()); +} + +string +JACKPortEngine::get_port_name (PortHandle port) const +{ + return jack_port_name ((jack_port_t*) port); +} + +PortEngine::PortHandle* +JACKPortEngine:: get_port_by_name (const std::string& name) const +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + return (PortHandle*) jack_port_by_name (_priv_jack, name.c_str()); +} + +void +JACKPortEngine::_registration_callback (jack_port_id_t /*id*/, int /*reg*/, void* arg) +{ + static_cast<JACKPortEngine*> (arg)->manager.registration_callback (); +} + +int +JACKPortEngine::_graph_order_callback (void *arg) +{ + return static_cast<JACKPortEngine*> (arg)->manager.graph_order_callback (); +} + +void +JACKPortEngine::_connect_callback (jack_port_id_t id_a, jack_port_id_t id_b, int conn, void* arg) +{ + static_cast<JACKPortEngine*> (arg)->connect_callback (id_a, id_b, conn); +} + +void +JACKPortEngine::connect_callback (jack_port_id_t id_a, jack_port_id_t id_b, int conn) +{ + if (manager.port_remove_in_progress()) { + return; + } + + GET_PRIVATE_JACK_POINTER (_priv_jack); + + jack_port_t* a = jack_port_by_id (_priv_jack, id_a); + jack_port_t* b = jack_port_by_id (_priv_jack, id_b); + + manager.connect_callback (jack_port_name (a), jack_port_name (b), conn == 0 ? false : true); +} + +bool +JACKPortEngine::connected (PortHandle port, bool process_callback_safe) +{ + bool ret = false; + + const char** ports; + + if (process_callback_safe) { + ports = jack_port_get_connections ((jack_port_t*)port); + } else { + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, false); + ports = jack_port_get_all_connections (_priv_jack, (jack_port_t*)port); + } + + if (ports) { + ret = true; + } + + jack_free (ports); + + return ret; +} + +bool +JACKPortEngine::connected_to (PortHandle port, const std::string& other, bool process_callback_safe) +{ + bool ret = false; + const char** ports; + + if (process_callback_safe) { + ports = jack_port_get_connections ((jack_port_t*)port); + } else { + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, false); + ports = jack_port_get_all_connections (_priv_jack, (jack_port_t*)port); + } + + if (ports) { + for (int i = 0; ports[i]; ++i) { + if (other == ports[i]) { + ret = true; + } + } + jack_free (ports); + } + + return ret; +} + +bool +JACKPortEngine::physically_connected (PortHandle p, bool process_callback_safe) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, false); + jack_port_t* port = (jack_port_t*) p; + + const char** ports; + + if (process_callback_safe) { + ports = jack_port_get_connections ((jack_port_t*)port); + } else { + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, false); + ports = jack_port_get_all_connections (_priv_jack, (jack_port_t*)port); + } + + if (ports) { + for (int i = 0; ports[i]; ++i) { + + jack_port_t* other = jack_port_by_name (_priv_jack, ports[i]); + + if (other && (jack_port_flags (other) & JackPortIsPhysical)) { + return true; + } + } + jack_free (ports); + } + + return false; +} + +int +JACKPortEngine::get_connections (PortHandle port, vector<string>& s, bool process_callback_safe) +{ + const char** ports; + + if (process_callback_safe) { + ports = jack_port_get_connections ((jack_port_t*)port); + } else { + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + ports = jack_port_get_all_connections (_priv_jack, (jack_port_t*)port); + } + + if (ports) { + for (int i = 0; ports[i]; ++i) { + s.push_back (ports[i]); + } + jack_free (ports); + } + + return s.size(); +} + +DataType +JACKPortEngine::port_data_type (PortHandle p) const +{ + return jack_port_type_to_ardour_data_type (jack_port_type ((jack_port_t*) p)); +} + +const string& +JACKPortEngine::my_name() const +{ + return _jack_connection->client_name(); +} + +bool +JACKPortEngine::port_is_physical (PortHandle ph) const +{ + if (!ph) { + return false; + } + + return jack_port_flags ((jack_port_t*) ph) & JackPortIsPhysical; +} + +int +JACKPortEngine::get_ports (const string& port_name_pattern, DataType type, PortFlags flags, vector<string>& s) const +{ + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack,0); + + const char** ports = jack_get_ports (_priv_jack, port_name_pattern.c_str(), + ardour_data_type_to_jack_port_type (type), + ardour_port_flags_to_jack_flags (flags)); + + if (ports == 0) { + return 0; + } + + for (uint32_t i = 0; ports[i]; ++i) { + s.push_back (ports[i]); + } + + jack_free (ports); + + return s.size(); +} + +ChanCount +JACKPortEngine::n_physical (unsigned long flags) const +{ + ChanCount c; + + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, c); + + const char ** ports = jack_get_ports (_priv_jack, NULL, NULL, JackPortIsPhysical | flags); + + if (ports) { + for (uint32_t i = 0; ports[i]; ++i) { + if (!strstr (ports[i], "Midi-Through")) { + DataType t = port_data_type (jack_port_by_name (_priv_jack, ports[i])); + if (t != DataType::NIL) { + c.set (t, c.get (t) + 1); + } + } + } + + jack_free (ports); + } + + return c; +} + +ChanCount +JACKPortEngine::n_physical_inputs () const +{ + return n_physical (JackPortIsInput); +} + +ChanCount +JACKPortEngine::n_physical_outputs () const +{ + return n_physical (JackPortIsOutput); +} + +void +JACKPortEngine::get_physical (DataType type, unsigned long flags, vector<string>& phy) const +{ + GET_PRIVATE_JACK_POINTER (_priv_jack); + const char ** ports; + + if ((ports = jack_get_ports (_priv_jack, NULL, ardour_data_type_to_jack_port_type (type), JackPortIsPhysical | flags)) == 0) { + return; + } + + if (ports) { + for (uint32_t i = 0; ports[i]; ++i) { + if (strstr (ports[i], "Midi-Through")) { + continue; + } + phy.push_back (ports[i]); + } + jack_free (ports); + } +} + +/** Get physical ports for which JackPortIsOutput is set; ie those that correspond to + * a physical input connector. + */ +void +JACKPortEngine::get_physical_inputs (DataType type, vector<string>& ins) +{ + get_physical (type, JackPortIsOutput, ins); +} + +/** Get physical ports for which JackPortIsInput is set; ie those that correspond to + * a physical output connector. + */ +void +JACKPortEngine::get_physical_outputs (DataType type, vector<string>& outs) +{ + get_physical (type, JackPortIsInput, outs); +} + + +bool +JACKPortEngine::can_monitor_input () const +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack,false); + const char ** ports; + + if ((ports = jack_get_ports (_priv_jack, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortCanMonitor)) == 0) { + return false; + } + + jack_free (ports); + + return true; +} + +int +JACKPortEngine::request_input_monitoring (PortHandle port, bool yn) +{ + return jack_port_request_monitor ((jack_port_t*) port, yn); +} +int +JACKPortEngine::ensure_input_monitoring (PortHandle port, bool yn) +{ + return jack_port_ensure_monitor ((jack_port_t*) port, yn); +} +bool +JACKPortEngine::monitoring_input (PortHandle port) +{ + return jack_port_monitoring_input ((jack_port_t*) port); +} + + +pframes_t +JACKPortEngine::sample_time_at_cycle_start () +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + return jack_last_frame_time (_priv_jack); +} + + +PortEngine::PortHandle +JACKPortEngine::register_port (const std::string& shortname, ARDOUR::DataType type, ARDOUR::PortFlags flags) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0); + return jack_port_register (_priv_jack, shortname.c_str(), + ardour_data_type_to_jack_port_type (type), + ardour_port_flags_to_jack_flags (flags), + 0); +} + +void +JACKPortEngine::unregister_port (PortHandle port) +{ + GET_PRIVATE_JACK_POINTER (_priv_jack); + (void) jack_port_unregister (_priv_jack, (jack_port_t*) port); +} + +int +JACKPortEngine::connect (PortHandle port, const std::string& other) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + return jack_connect (_priv_jack, jack_port_name ((jack_port_t*) port), other.c_str()); +} +int +JACKPortEngine::connect (const std::string& src, const std::string& dst) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + + int r = jack_connect (_priv_jack, src.c_str(), dst.c_str()); + return r; +} + +int +JACKPortEngine::disconnect (PortHandle port, const std::string& other) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + return jack_disconnect (_priv_jack, jack_port_name ((jack_port_t*) port), other.c_str()); +} + +int +JACKPortEngine::disconnect (const std::string& src, const std::string& dst) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + return jack_disconnect (_priv_jack, src.c_str(), dst.c_str()); +} + +int +JACKPortEngine::disconnect_all (PortHandle port) +{ + GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1); + return jack_port_disconnect (_priv_jack, (jack_port_t*) port); +} + +int +JACKPortEngine::midi_event_get (pframes_t& timestamp, size_t& size, uint8_t** buf, void* port_buffer, uint32_t event_index) +{ + jack_midi_event_t ev; + int ret; + + if ((ret = jack_midi_event_get (&ev, port_buffer, event_index)) == 0) { + timestamp = ev.time; + size = ev.size; + *buf = ev.buffer; + } + + return ret; +} + +int +JACKPortEngine::midi_event_put (void* port_buffer, pframes_t timestamp, const uint8_t* buffer, size_t size) +{ + return jack_midi_event_write (port_buffer, timestamp, buffer, size); +} + +uint32_t +JACKPortEngine::get_midi_event_count (void* port_buffer) +{ + return jack_midi_get_event_count (port_buffer); +} + +void +JACKPortEngine::midi_clear (void* port_buffer) +{ + jack_midi_clear_buffer (port_buffer); +} + +void +JACKPortEngine::set_latency_range (PortHandle port, bool for_playback, LatencyRange r) +{ + jack_latency_range_t range; + + range.min = r.min; + range.max = r.max; + + jack_port_set_latency_range ((jack_port_t*) port, for_playback ? JackPlaybackLatency : JackCaptureLatency, &range); +} + +LatencyRange +JACKPortEngine::get_latency_range (PortHandle port, bool for_playback) +{ + jack_latency_range_t range; + LatencyRange ret; + + jack_port_get_latency_range ((jack_port_t*) port, for_playback ? JackPlaybackLatency : JackCaptureLatency, &range); + + ret.min = range.min; + ret.max = range.max; + + return ret; +} + +void* +JACKPortEngine::get_buffer (PortHandle port, pframes_t nframes) +{ + return jack_port_get_buffer ((jack_port_t*) port, nframes); +} + +uint32_t +JACKPortEngine::port_name_size() const +{ + return jack_port_name_size (); +} diff --git a/libs/backends/jack/jack_portengine.h b/libs/backends/jack/jack_portengine.h new file mode 100644 index 0000000000..0e1eb48c5c --- /dev/null +++ b/libs/backends/jack/jack_portengine.h @@ -0,0 +1,132 @@ +/* + Copyright (C) 2013 Paul Davis + + 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. + +*/ + +#ifndef __libardour_jack_portengine_h__ +#define __libardour_jack_portengine_h__ + +#include <string> +#include <vector> + +#include <stdint.h> + +#include <jack/types.h> +#include <jack/midiport.h> + +#include <boost/shared_ptr.hpp> + +#include "pbd/signals.h" + +#include "ardour/port_engine.h" +#include "ardour/types.h" + +namespace ARDOUR { + +class JackConnection; + +class JACKPortEngine : public PortEngine +{ + public: + JACKPortEngine (PortManager&, boost::shared_ptr<JackConnection>); + ~JACKPortEngine(); + + void* private_handle() const; + bool connected() const; + + const std::string& my_name() const; + + uint32_t port_name_size() const; + + int set_port_name (PortHandle, const std::string&); + std::string get_port_name (PortHandle) const; + PortHandle* get_port_by_name (const std::string&) const; + + int get_ports (const std::string& port_name_pattern, DataType type, PortFlags flags, std::vector<std::string>&) const; + + DataType port_data_type (PortHandle) const; + + PortHandle register_port (const std::string& shortname, ARDOUR::DataType, ARDOUR::PortFlags); + void unregister_port (PortHandle); + + bool connected (PortHandle, bool process_callback_safe); + bool connected_to (PortHandle, const std::string&, bool process_callback_safe); + bool physically_connected (PortHandle, bool process_callback_safe); + int get_connections (PortHandle, std::vector<std::string>&, bool process_callback_safe); + int connect (PortHandle, const std::string&); + + int disconnect (PortHandle, const std::string&); + int disconnect_all (PortHandle); + int connect (const std::string& src, const std::string& dst); + int disconnect (const std::string& src, const std::string& dst); + + /* MIDI */ + + int midi_event_get (pframes_t& timestamp, size_t& size, uint8_t** buf, void* port_buffer, uint32_t event_index); + int midi_event_put (void* port_buffer, pframes_t timestamp, const uint8_t* buffer, size_t size); + uint32_t get_midi_event_count (void* port_buffer); + void midi_clear (void* port_buffer); + + /* Monitoring */ + + bool can_monitor_input() const; + int request_input_monitoring (PortHandle, bool); + int ensure_input_monitoring (PortHandle, bool); + bool monitoring_input (PortHandle); + + /* Latency management + */ + + void set_latency_range (PortHandle, bool for_playback, LatencyRange); + LatencyRange get_latency_range (PortHandle, bool for_playback); + + /* Physical ports */ + + bool port_is_physical (PortHandle) const; + void get_physical_outputs (DataType type, std::vector<std::string>&); + void get_physical_inputs (DataType type, std::vector<std::string>&); + ChanCount n_physical_outputs () const; + ChanCount n_physical_inputs () const; + + /* Getting access to the data buffer for a port */ + + void* get_buffer (PortHandle, pframes_t); + + /* Miscellany */ + + pframes_t sample_time_at_cycle_start (); + + private: + boost::shared_ptr<JackConnection> _jack_connection; + + static int _graph_order_callback (void *arg); + static void _registration_callback (jack_port_id_t, int, void *); + static void _connect_callback (jack_port_id_t, jack_port_id_t, int, void *); + + void connect_callback (jack_port_id_t, jack_port_id_t, int); + + ChanCount n_physical (unsigned long flags) const; + void get_physical (DataType type, unsigned long flags, std::vector<std::string>& phy) const; + + PBD::ScopedConnection jack_connection_connection; + void connected_to_jack (); + +}; + +} // namespace + +#endif /* __libardour_jack_portengine_h__ */ diff --git a/libs/backends/jack/jack_utils.cc b/libs/backends/jack/jack_utils.cc new file mode 100644 index 0000000000..77f3d95aa1 --- /dev/null +++ b/libs/backends/jack/jack_utils.cc @@ -0,0 +1,897 @@ +/* + 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 HAVE_ALSA +#include <alsa/asoundlib.h> +#endif + +#ifdef __APPLE__ +#include <CoreAudio/CoreAudio.h> +#include <CoreFoundation/CFString.h> +#include <sys/param.h> +#include <mach-o/dyld.h> +#endif + +#ifdef HAVE_PORTAUDIO +#include <portaudio.h> +#endif + +#include <jack/jack.h> + +#include <fstream> + +#include <boost/scoped_ptr.hpp> + +#include <glibmm/miscutils.h> + +#include "pbd/epa.h" +#include "pbd/error.h" +#include "pbd/convert.h" +#include "pbd/file_utils.h" +#include "pbd/search_path.h" + +#include "jack_utils.h" + +#ifdef __APPLE +#include <CFBundle.h> +#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<string>& 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<string> drivers; + get_jack_audio_driver_names (drivers); + audio_driver_name = drivers.front (); +} + +void +ARDOUR::get_jack_sample_rate_strings (vector<string>& 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<std::string>& 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<string>& 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; +} + +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) { + + if (snd_ctl_card_info (handle, info) < 0) { + continue; + } + + string card_name = snd_ctl_card_info_get_name (info); + + /* change devname to use ID, not number */ + + devname = "hw:"; + devname += snd_ctl_card_info_get_id (info); + + while (snd_ctl_pcm_next_device (handle, &device) >= 0 && device >= 0) { + + /* only detect duplex devices here. more + * complex arrangements are beyond our scope + */ + + snd_pcm_info_set_device (pcminfo, device); + snd_pcm_info_set_subdevice (pcminfo, 0); + snd_pcm_info_set_stream (pcminfo, SND_PCM_STREAM_CAPTURE); + + if (snd_ctl_pcm_info (handle, pcminfo) >= 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 (card_name, devname)); + } + } + } + + snd_ctl_close(handle); + } + } +#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) +{ +#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(); +#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_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<std::string> +ARDOUR::get_jack_device_names_for_audio_driver (const string& driver_name) +{ + std::vector<std::string> 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& server_dir_paths, + const vector<string>& server_names, + vector<std::string>& server_paths) +{ + for (vector<string>::const_iterator i = server_names.begin(); i != server_names.end(); ++i) { + Glib::PatternSpec ps (*i); + find_matching_files_in_directories (server_dir_paths, ps, server_paths); + } + return !server_paths.empty(); +} + +bool +ARDOUR::get_jack_server_paths (vector<std::string>& server_paths) +{ + vector<std::string> server_dirs; + + if (!get_jack_server_dir_paths (server_dirs)) { + return false; + } + + vector<string> 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<std::string> 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<string> 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 (options.input_channels) { + args.push_back ("-i"); + args.push_back (to_string (options.input_channels, std::dec)); + } + + if (options.output_channels) { + args.push_back ("-o"); + args.push_back (to_string (options.output_channels, std::dec)); + } + + 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 ("-O"); + 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<string>::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; +} diff --git a/libs/backends/jack/jack_utils.h b/libs/backends/jack/jack_utils.h new file mode 100644 index 0000000000..7565353198 --- /dev/null +++ b/libs/backends/jack/jack_utils.h @@ -0,0 +1,235 @@ +/* + 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 <stdint.h> + +#include <vector> +#include <map> +#include <string> + +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<std::string>& 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 samplerates supported be JACK + */ + void get_jack_sample_rate_strings (std::vector<std::string>& 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<std::string>& 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<std::string>& 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); + + /** + * Key being a readable name to display in a GUI + * Value being name used in a jack commandline + */ + typedef std::map<std::string, std::string> 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<std::string> 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<std::string>& 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<std::string>&); + + /** + * 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<std::string>& 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<std::string>& server_dir_paths, + const std::vector<std::string>& server_names, + std::vector<std::string>& server_paths); + + + bool get_jack_server_paths (std::vector<std::string>& 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_channels; + uint32_t output_channels; + 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); +} diff --git a/libs/backends/jack/wscript b/libs/backends/jack/wscript new file mode 100644 index 0000000000..3c47f3a652 --- /dev/null +++ b/libs/backends/jack/wscript @@ -0,0 +1,51 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import os +import sys +import re + +# Library version (UNIX style major, minor, micro) +# major increment <=> incompatible changes +# minor increment <=> compatible changes (additions) +# micro increment <=> no interface changes +JACKBACKEND_VERSION = '1.0.0' +I18N_PACKAGE = 'jack-backend' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + autowaf.set_options(opt) + +def configure(conf): + autowaf.configure(conf) + +def build(bld): + obj = bld(features = 'cxx cxxshlib') + obj.source = [ + 'jack_api.cc', + 'jack_connection.cc', + 'jack_audiobackend.cc', + 'jack_portengine.cc', + 'jack_utils.cc' + ] + obj.includes = ['.'] + obj.cxxflags = [ '-fPIC' ] + obj.name = 'jack_audiobackend' + obj.target = 'jack_audiobackend' + obj.uselib = [ 'JACK' ] + obj.use = 'ardour libpbd' + obj.vnum = JACKBACKEND_VERSION + obj.install_path = os.path.join(bld.env['LIBDIR'], 'ardour3', 'backends') + obj.defines = ['PACKAGE="' + I18N_PACKAGE + '"'] + + + # + # device discovery code in the jack backend needs ALSA + # on Linux. + # + + if re.search ("linux", sys.platform) != None: + obj.uselib += [ 'ALSA' ] + diff --git a/libs/backends/wscript b/libs/backends/wscript new file mode 100644 index 0000000000..b8779b7713 --- /dev/null +++ b/libs/backends/wscript @@ -0,0 +1,27 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +import os + +# Mandatory variables +top = '.' +out = 'build' + +backends = [ 'jack' ] + +def options(opt): + autowaf.set_options(opt) + +def sub_config_and_use(conf, name, has_objects = True): + conf.recurse(name) + autowaf.set_local_lib(conf, name, has_objects) + +def configure(conf): + autowaf.set_recursive() + autowaf.configure(conf) + + for i in backends: + sub_config_and_use(conf, i) + +def build(bld): + for i in backends: + bld.recurse(i) |