Paul Davis
2a89234027
Issues remain with the basic model of the AMS dialog - when is newly chosen state pushed into the backend (which can then modify the control app button sensitivity. This is a special problem for this button because APIs like ASIO and CoreAudio probably don't allow us to launch a control app for an arbitrary device, but only one actually in use. In this sense it is different from properties like available buffer size etc, where we can typically query without actually using the device.
1003 lines
21 KiB
C++
1003 lines
21 KiB
C++
/*
|
|
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 = "mudita";
|
|
}
|
|
}
|
|
} 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);
|
|
}
|