Paul Davis
0eed821eb9
Because we use the non-callback API, we can call our thread init callback ourselves from ::process_thread(). In addition, the init_callback in JACK is used by every thread JACK creates, including the messagebuffer thread, and this confuses things from an Ardour POV where the callback was intended just for realtime threads.
1245 lines
28 KiB
C++
1245 lines
28 KiB
C++
/*
|
|
* Copyright (C) 2013-2015 Tim Mayberry <mojofunk@gmail.com>
|
|
* Copyright (C) 2013-2017 Paul Davis <paul@linuxaudiosystems.com>
|
|
* Copyright (C) 2014-2018 Robin Gareus <robin@gareus.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#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 "ardour/audioengine.h"
|
|
#include "ardour/debug.h"
|
|
#include "ardour/session.h"
|
|
#include "ardour/types.h"
|
|
|
|
#include "jack_audiobackend.h"
|
|
#include "jack_connection.h"
|
|
#include "jack_utils.h"
|
|
#include "jack_session.h"
|
|
|
|
#include "pbd/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; }
|
|
|
|
JACKAudioBackend::JACKAudioBackend (AudioEngine& e, AudioBackendInfo& info, std::shared_ptr<JackConnection> jc)
|
|
: AudioBackend (e, info)
|
|
, _jack_connection (jc)
|
|
, _running (false)
|
|
, _freewheeling (false)
|
|
, _target_sample_rate (48000)
|
|
, _target_buffer_size (1024)
|
|
, _target_num_periods (2)
|
|
, _target_interleaved (false)
|
|
, _target_input_channels (0)
|
|
, _target_output_channels (0)
|
|
, _target_systemic_input_latency (0)
|
|
, _target_systemic_output_latency (0)
|
|
, _current_sample_rate (0)
|
|
, _current_buffer_size (0)
|
|
, _jack_ports (new JackPorts)
|
|
, _session (0)
|
|
{
|
|
_jack_connection->Connected.connect_same_thread (jack_connection_connection, boost::bind (&JACKAudioBackend::when_connected_to_jack, this));
|
|
_jack_connection->Disconnected.connect_same_thread (disconnect_connection, boost::bind (&JACKAudioBackend::disconnected, this, _1));
|
|
}
|
|
|
|
JACKAudioBackend::~JACKAudioBackend()
|
|
{
|
|
{
|
|
RCUWriter<JackPorts> writer (_jack_ports);
|
|
std::shared_ptr<JackPorts> jp = writer.get_copy ();
|
|
jp->clear ();
|
|
}
|
|
|
|
_jack_ports.flush ();
|
|
}
|
|
|
|
string
|
|
JACKAudioBackend::name() const
|
|
{
|
|
return X_("JACK");
|
|
}
|
|
|
|
void*
|
|
JACKAudioBackend::private_handle() const
|
|
{
|
|
return _jack_connection->jack();
|
|
}
|
|
|
|
bool
|
|
JACKAudioBackend::available() 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 (device == _target_device && available()) {
|
|
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 (device == _target_device && available()) {
|
|
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;
|
|
}
|
|
|
|
std::vector<uint32_t>
|
|
JACKAudioBackend::available_period_sizes (const std::string& driver, const std::string&) const
|
|
{
|
|
vector<uint32_t> s;
|
|
if (ARDOUR::get_jack_audio_driver_supports_setting_period_count (driver)) {
|
|
s.push_back (2);
|
|
s.push_back (3);
|
|
}
|
|
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 (available()) {
|
|
/* 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 (!available()) {
|
|
_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_peridod_size (uint32_t nperiods)
|
|
{
|
|
if (!available()) {
|
|
_target_num_periods = nperiods;
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
JACKAudioBackend::set_buffer_size (uint32_t nframes)
|
|
{
|
|
if (!available()) {
|
|
_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_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 (available()) {
|
|
if (cnt != 0) {
|
|
/* can't set a real value for this while JACK runs */
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
_target_input_channels = cnt;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
JACKAudioBackend::set_output_channels (uint32_t cnt)
|
|
{
|
|
if (available()) {
|
|
if (cnt != 0) {
|
|
/* can't set a real value for this while JACK runs */
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
_target_output_channels = cnt;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
JACKAudioBackend::set_systemic_input_latency (uint32_t l)
|
|
{
|
|
if (available()) {
|
|
/* can't do this while JACK runs */
|
|
return -1;
|
|
}
|
|
|
|
_target_systemic_input_latency = l;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
JACKAudioBackend::set_systemic_output_latency (uint32_t l)
|
|
{
|
|
if (available()) {
|
|
/* can't do this while JACK runs */
|
|
return -1;
|
|
}
|
|
|
|
_target_systemic_output_latency = l;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* --- Parameter retrieval --- */
|
|
|
|
std::string
|
|
JACKAudioBackend::device_name () const
|
|
{
|
|
if (!_jack_connection->in_control()) {
|
|
return "???"; // JACK has no way (as of fall 2013) to return
|
|
// the device name
|
|
}
|
|
|
|
return _target_device;
|
|
}
|
|
|
|
std::string
|
|
JACKAudioBackend::driver_name() const
|
|
{
|
|
if (!_jack_connection->in_control()) {
|
|
return "???"; // JACK has no way (as of fall 2013) to return
|
|
// the driver name
|
|
}
|
|
|
|
return _target_driver;
|
|
}
|
|
|
|
float
|
|
JACKAudioBackend::sample_rate () const
|
|
{
|
|
if (!_jack_connection->in_control()) {
|
|
if (available()) {
|
|
return _current_sample_rate;
|
|
} else {
|
|
return _jack_connection->probed_sample_rate ();
|
|
}
|
|
}
|
|
return _target_sample_rate;
|
|
}
|
|
|
|
uint32_t
|
|
JACKAudioBackend::buffer_size () const
|
|
{
|
|
if (!_jack_connection->in_control()) {
|
|
if (available()) {
|
|
return _current_buffer_size;
|
|
} else {
|
|
return _jack_connection->probed_buffer_size ();
|
|
}
|
|
}
|
|
return _target_buffer_size;
|
|
}
|
|
|
|
uint32_t
|
|
JACKAudioBackend::period_size () const
|
|
{
|
|
return _target_num_periods;
|
|
}
|
|
|
|
bool
|
|
JACKAudioBackend::interleaved () const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
string
|
|
JACKAudioBackend::midi_option () const
|
|
{
|
|
return _target_midi_option;
|
|
}
|
|
|
|
uint32_t
|
|
JACKAudioBackend::input_channels () const
|
|
{
|
|
if (!_jack_connection->in_control()) {
|
|
if (available()) {
|
|
return n_physical (JackPortIsInput).n_audio();
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (available()) {
|
|
return n_physical (JackPortIsInput).n_audio();
|
|
} else {
|
|
return _target_input_channels;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t
|
|
JACKAudioBackend::output_channels () const
|
|
{
|
|
if (!_jack_connection->in_control()) {
|
|
if (available()) {
|
|
return n_physical (JackPortIsOutput).n_audio();
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (available()) {
|
|
return n_physical (JackPortIsOutput).n_audio();
|
|
} else {
|
|
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 (bool for_latency_measurement)
|
|
{
|
|
/* 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 = _target_num_periods;
|
|
options.input_device = _target_device;
|
|
options.output_device = _target_device;
|
|
if (for_latency_measurement) {
|
|
options.input_latency = 0;
|
|
options.output_latency = 0;
|
|
} else {
|
|
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;
|
|
|
|
ARDOUR::set_midi_option (options, _target_midi_option);
|
|
|
|
/* 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 - we will still try to start JACK
|
|
* automatically but it will be without our preferred options
|
|
*/
|
|
std::cerr << "get_jack_command_line_string () failed: using default settings." << std::endl;
|
|
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 (bool for_latency_measurement)
|
|
{
|
|
if (!available()) {
|
|
|
|
if (_jack_connection->in_control()) {
|
|
/* we will be starting JACK, so set up the
|
|
command that JACK will use when it (auto-)starts
|
|
*/
|
|
setup_jack_startup_command (for_latency_measurement);
|
|
}
|
|
|
|
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
|
|
*/
|
|
|
|
if (engine.reestablish_ports ()) {
|
|
error << _("Could not re-establish ports after connecting to JACK") << endmsg;
|
|
return -1;
|
|
}
|
|
|
|
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 ()
|
|
{
|
|
_running = false; // no 'engine halted message'.
|
|
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::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 = onoff;
|
|
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 (samplepos_t where)
|
|
{
|
|
GET_PRIVATE_JACK_POINTER (_priv_jack);
|
|
jack_transport_locate (_priv_jack, where);
|
|
}
|
|
|
|
samplepos_t
|
|
JACKAudioBackend::transport_sample () 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;
|
|
}
|
|
|
|
samplepos_t
|
|
JACKAudioBackend::sample_time ()
|
|
{
|
|
GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0);
|
|
return jack_frame_time (_priv_jack);
|
|
}
|
|
|
|
samplepos_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);
|
|
|
|
/* no need to set the thread_init_callback because we use the
|
|
* non-callback API, and run the thread init callback in our own code.
|
|
*/
|
|
|
|
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) {
|
|
JACKSession jsession (session);
|
|
jsession.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;
|
|
bool tstate_valid = true;
|
|
|
|
switch (state) {
|
|
case JackTransportRolling:
|
|
tstate = TransportRolling;
|
|
break;
|
|
case JackTransportLooping:
|
|
tstate = TransportLooping;
|
|
break;
|
|
case JackTransportStarting:
|
|
tstate = TransportStarting;
|
|
break;
|
|
case JackTransportStopped:
|
|
tstate = TransportStopped;
|
|
break;
|
|
default:
|
|
// ignore "unofficial" states like JackTransportNetStarting (jackd2)
|
|
tstate_valid = false;
|
|
break;
|
|
}
|
|
|
|
if (tstate_valid) {
|
|
return engine.sync_callback (tstate, pos->frame);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int
|
|
JACKAudioBackend::_xrun_callback (void *arg)
|
|
{
|
|
JACKAudioBackend* jab = static_cast<JACKAudioBackend*> (arg);
|
|
if (jab->available()) {
|
|
jab->engine.Xrun (); /* EMIT SIGNAL */
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
JACKAudioBackend::_session_callback (jack_session_event_t *event, void *arg)
|
|
{
|
|
JACKAudioBackend* jab = static_cast<JACKAudioBackend*> (arg);
|
|
ARDOUR::Session* session = jab->engine.session();
|
|
|
|
if (session) {
|
|
JACKSession jsession (session);
|
|
jsession.session_event (event);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1);
|
|
|
|
jack_native_thread_t thread_id;
|
|
ThreadData* td = new ThreadData (this, f, thread_stack_size());
|
|
|
|
if (jack_client_create_thread (_priv_jack, &thread_id, jack_client_real_time_priority (_priv_jack),
|
|
jack_is_realtime (_priv_jack), _start_process_thread, td)) {
|
|
return -1;
|
|
}
|
|
|
|
_jack_threads.push_back(thread_id);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
JACKAudioBackend::join_process_threads ()
|
|
{
|
|
GET_PRIVATE_JACK_POINTER_RET (_priv_jack, -1);
|
|
|
|
int ret = 0;
|
|
|
|
for (std::vector<jack_native_thread_t>::const_iterator i = _jack_threads.begin ();
|
|
i != _jack_threads.end(); i++) {
|
|
|
|
#if defined(USING_JACK2_EXPANSION_OF_JACK_API) || defined __jack_systemdeps_h__
|
|
// jack_client is not used by JACK2's implementation
|
|
// also jack_client_close() leaves threads active
|
|
if (jack_client_stop_thread (_priv_jack, *i) != 0)
|
|
#else
|
|
void* status;
|
|
if (pthread_join (*i, &status) != 0)
|
|
#endif
|
|
{
|
|
error << "AudioEngine: cannot stop process thread" << endmsg;
|
|
ret += -1;
|
|
}
|
|
}
|
|
|
|
_jack_threads.clear();
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
JACKAudioBackend::in_process_thread ()
|
|
{
|
|
#if defined COMPILER_MINGW && (!defined PTW32_VERSION || defined __jack_systemdeps_h__)
|
|
if (_main_thread == GetCurrentThread()) {
|
|
return true;
|
|
}
|
|
#else // pthreads
|
|
if (pthread_equal (_main_thread, pthread_self()) != 0) {
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
for (std::vector<jack_native_thread_t>::const_iterator i = _jack_threads.begin ();
|
|
i != _jack_threads.end(); i++) {
|
|
|
|
#if defined COMPILER_MINGW && (!defined PTW32_VERSION || defined __jack_systemdeps_h__)
|
|
if (*i == GetCurrentThread()) {
|
|
return true;
|
|
}
|
|
#else // pthreads
|
|
if (pthread_equal (*i, pthread_self()) != 0) {
|
|
return true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
uint32_t
|
|
JACKAudioBackend::process_thread_count ()
|
|
{
|
|
return _jack_threads.size();
|
|
}
|
|
|
|
int
|
|
JACKAudioBackend::client_real_time_priority ()
|
|
{
|
|
GET_PRIVATE_JACK_POINTER_RET (_priv_jack, 0);
|
|
return jack_client_real_time_priority (_priv_jack);
|
|
}
|
|
|
|
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
|
|
*/
|
|
|
|
#if defined COMPILER_MINGW && (!defined PTW32_VERSION || defined __jack_systemdeps_h__)
|
|
_main_thread = GetCurrentThread();
|
|
#else
|
|
_main_thread = pthread_self ();
|
|
#endif
|
|
|
|
|
|
AudioEngine::thread_init_callback (this);
|
|
|
|
while (1) {
|
|
GET_PRIVATE_JACK_POINTER_RET(_priv_jack,0);
|
|
dsp_stats[AudioBackend::DeviceWait].start ();
|
|
pframes_t nframes = jack_cycle_wait (_priv_jack);
|
|
dsp_stats[AudioBackend::DeviceWait].update ();
|
|
dsp_stats[RunLoop].start();
|
|
|
|
if (engine.process_callback (nframes)) {
|
|
return 0;
|
|
}
|
|
|
|
jack_cycle_signal (_priv_jack, 0);
|
|
dsp_stats[AudioBackend::RunLoop].update ();
|
|
}
|
|
|
|
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::dsp_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")) {
|
|
std::shared_ptr<JackPort> jp (new JackPort (jack_port_by_name (_priv_jack, ports[i])));
|
|
DataType t = port_data_type (jp);
|
|
if (t != DataType::NIL) {
|
|
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 if (_target_device == "M2496") {
|
|
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);
|
|
}
|
|
|
|
vector<string>
|
|
JACKAudioBackend::enumerate_midi_options () const
|
|
{
|
|
return ARDOUR::enumerate_midi_options ();
|
|
}
|
|
|
|
int
|
|
JACKAudioBackend::set_midi_option (const string& opt)
|
|
{
|
|
_target_midi_option = opt;
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
JACKAudioBackend::speed_and_position (double& speed, samplepos_t& position)
|
|
{
|
|
jack_position_t pos;
|
|
jack_transport_state_t state;
|
|
bool starting;
|
|
|
|
/* this won't be called if the port engine in use is not JACK, so we do
|
|
not have to worry about the type of PortEngine::private_handle()
|
|
*/
|
|
|
|
speed = 0;
|
|
position = 0;
|
|
|
|
GET_PRIVATE_JACK_POINTER_RET (_priv_jack, true);
|
|
|
|
state = jack_transport_query (_priv_jack, &pos);
|
|
|
|
switch (state) {
|
|
case JackTransportStopped:
|
|
speed = 0;
|
|
starting = false;
|
|
break;
|
|
case JackTransportRolling:
|
|
speed = 1.0;
|
|
starting = false;
|
|
break;
|
|
case JackTransportLooping:
|
|
speed = 1.0;
|
|
starting = false;
|
|
break;
|
|
case JackTransportStarting:
|
|
starting = true;
|
|
// don't adjust speed here, just leave it as it was
|
|
break;
|
|
default:
|
|
starting = true; // jack2: JackTransportNetStarting
|
|
std::cerr << "WARNING: Unknown JACK transport state: " << state << std::endl;
|
|
}
|
|
|
|
position = pos.frame;
|
|
|
|
DEBUG_TRACE (DEBUG::Slave, string_compose ("JACK transport: speed %1 position %2 starting %3\n", speed, position, starting));
|
|
|
|
return starting;
|
|
}
|
|
|
|
int
|
|
JACKAudioBackend::reset_device ()
|
|
{
|
|
/* XXX need to figure out what this means for JACK
|
|
*/
|
|
return 0;
|
|
}
|