13
0
livetrax/libs/ardour/audioengine.cc

1631 lines
37 KiB
C++

/*
* Copyright (C) 2005-2019 Paul Davis <paul@linuxaudiosystems.com>
* Copyright (C) 2006-2016 David Robillard <d@drobilla.net>
* Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
* Copyright (C) 2008-2010 Sakari Bergen <sakari.bergen@beatwaves.net>
* Copyright (C) 2008 Hans Baier <hansfbaier@googlemail.com>
* Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
* Copyright (C) 2013-2014 John Emmas <john@creativepost.co.uk>
* Copyright (C) 2013-2015 Tim Mayberry <mojofunk@gmail.com>
* Copyright (C) 2015 GZharun <grygoriiz@wavesglobal.com>
*
* 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 <unistd.h>
#include <cerrno>
#include <vector>
#include <exception>
#include <stdexcept>
#include <sstream>
#include <cmath>
#include <glibmm/timer.h>
#include <glibmm/pattern.h>
#include <glibmm/module.h>
#include "pbd/epa.h"
#include "pbd/file_utils.h"
#include "pbd/pthread_utils.h"
#include "pbd/stacktrace.h"
#include "pbd/unknown_type.h"
#include "midi++/port.h"
#include "midi++/mmc.h"
#include "ardour/async_midi_port.h"
#include "ardour/ardour.h"
#include "ardour/audio_port.h"
#include "ardour/audio_backend.h"
#include "ardour/audioengine.h"
#include "ardour/search_paths.h"
#include "ardour/buffer.h"
#include "ardour/cycle_timer.h"
#include "ardour/internal_send.h"
#include "ardour/meter.h"
#include "ardour/midi_port.h"
#include "ardour/midiport_manager.h"
#include "ardour/mididm.h"
#include "ardour/mtdm.h"
#include "ardour/port.h"
#include "ardour/process_thread.h"
#include "ardour/rc_configuration.h"
#include "ardour/session.h"
#include "ardour/transport_master_manager.h"
#include "pbd/i18n.h"
using namespace std;
using namespace ARDOUR;
using namespace PBD;
AudioEngine* AudioEngine::_instance = 0;
static gint audioengine_thread_cnt = 1;
#ifdef SILENCE_AFTER
#define SILENCE_AFTER_SECONDS 600
#endif
AudioEngine::AudioEngine ()
: session_remove_pending (false)
, session_removal_countdown (-1)
, _running (false)
, _freewheeling (false)
, monitor_check_interval (INT32_MAX)
, last_monitor_check (0)
, _processed_samples (-1)
, m_meter_thread (0)
, _main_thread (0)
, _mtdm (0)
, _mididm (0)
, _measuring_latency (MeasureNone)
, _latency_flush_samples (0)
, _latency_signal_latency (0)
, _stopped_for_latency (false)
, _started_for_latency (false)
, _in_destructor (false)
, _last_backend_error_string(AudioBackend::get_error_string(AudioBackend::NoError))
, _hw_reset_event_thread(0)
, _hw_reset_request_count(0)
, _stop_hw_reset_processing(0)
, _hw_devicelist_update_thread(0)
, _hw_devicelist_update_count(0)
, _stop_hw_devicelist_processing(0)
, _start_cnt (0)
, _init_countdown (0)
#ifdef SILENCE_AFTER_SECONDS
, _silence_countdown (0)
, _silence_hit_cnt (0)
#endif
{
reset_silence_countdown ();
start_hw_event_processing();
discover_backends ();
}
AudioEngine::~AudioEngine ()
{
_in_destructor = true;
stop_hw_event_processing();
drop_backend ();
for (BackendMap::const_iterator i = _backends.begin(); i != _backends.end(); ++i) {
i->second->deinstantiate();
}
delete _main_thread;
}
AudioEngine*
AudioEngine::create ()
{
if (_instance) {
return _instance;
}
_instance = new AudioEngine ();
return _instance;
}
void
AudioEngine::split_cycle (pframes_t nframes)
{
/* caller must hold process lock */
boost::shared_ptr<Ports> p = ports.reader();
/* This is mainly for the benefit of rt-control ports (MTC, MClk)
*
* Normally ports are flushed by the route:
* ARDOUR::MidiPort::flush_buffers(unsigned int)
* ARDOUR::Delivery::flush_buffers(long)
* ARDOUR::Route::flush_processor_buffers_locked(long)
* ARDOUR::Route::run_route(long, long, unsigned int, bool, bool)
* ...
*
* This is required so that route -> route connections work during
* normal processing.
*
* However some non-route ports may contain MIDI events
* from current Port::port_offset() .. Port::port_offset() + nframes.
* If those events are not pushed to ports before the cycle split,
* MidiPort::flush_buffers will drop them (event time is out of bounds).
*
* TODO: for optimized builds MidiPort::flush_buffers() could
* be relaxed, ignore ev->time() checks, and simply send
* all events as-is.
*/
for (Ports::iterator i = p->begin(); i != p->end(); ++i) {
i->second->flush_buffers (nframes);
}
Port::increment_global_port_buffer_offset (nframes);
/* tell all Ports that we're going to start a new (split) cycle */
for (Ports::iterator i = p->begin(); i != p->end(); ++i) {
i->second->cycle_split ();
}
}
int
AudioEngine::sample_rate_change (pframes_t nframes)
{
/* check for monitor input change every 1/10th of second */
monitor_check_interval = nframes / 10;
last_monitor_check = 0;
if (_session) {
_session->set_sample_rate (nframes);
}
SampleRateChanged (nframes); /* EMIT SIGNAL */
#ifdef SILENCE_AFTER_SECONDS
_silence_countdown = nframes * SILENCE_AFTER_SECONDS;
#endif
return 0;
}
int
AudioEngine::buffer_size_change (pframes_t bufsiz)
{
set_port_buffer_sizes (bufsiz);
if (_session) {
_session->set_block_size (bufsiz);
last_monitor_check = 0;
}
BufferSizeChanged (bufsiz); /* EMIT SIGNAL */
return 0;
}
/** Method called by our ::process_thread when there is work to be done.
* @param nframes Number of samples to process.
*/
#ifdef __clang__
__attribute__((annotate("realtime")))
#endif
int
AudioEngine::process_callback (pframes_t nframes)
{
Glib::Threads::Mutex::Lock tm (_process_lock, Glib::Threads::TRY_LOCK);
Port::set_speed_ratio (1.0);
PT_TIMING_REF;
PT_TIMING_CHECK (1);
/// The number of samples that will have been processed when we've finished
pframes_t next_processed_samples;
if (_processed_samples < 0) {
_processed_samples = sample_time();
cerr << "IIIIINIT PS to " << _processed_samples << endl;
}
/* handle wrap around of total samples counter */
if (max_samplepos - _processed_samples < nframes) {
next_processed_samples = nframes - (max_samplepos - _processed_samples);
} else {
next_processed_samples = _processed_samples + nframes;
}
if (!tm.locked()) {
/* return having done nothing */
if (_session) {
Xrun();
}
/* really only JACK requires this
* (other backends clear the output buffers
* before the process_callback. it may even be
* jack/alsa only). but better safe than sorry.
*/
PortManager::silence_outputs (nframes);
return 0;
}
/* The coreaudio-backend calls thread_init_callback() if
* the hardware changes or pthread_self() changes.
*
* However there are cases when neither holds true, yet
* the thread-pool changes: e.g. connect a headphone to
* a shared mic/headphone jack.
* It's probably related to, or caused by clocksource changes.
*
* For reasons yet unknown Glib::Threads::Private() can
* use a different thread-private in the same pthread
* (coreaudio render callback).
*
* Coreaudio must set something which influences
* pthread_key_t uniqness or reset the key using
* pthread_getspecific().
*/
if (! SessionEvent::has_per_thread_pool ()) {
thread_init_callback (NULL);
}
if (_session && _init_countdown > 0) {
--_init_countdown;
/* Warm up caches */
PortManager::cycle_start (nframes);
_session->process (nframes);
PortManager::silence (nframes);
PortManager::cycle_end (nframes);
if (_init_countdown == 0) {
_session->reset_xrun_count();
}
return 0;
}
bool return_after_remove_check = false;
if (_measuring_latency == MeasureAudio && _mtdm) {
/* run a normal cycle from the perspective of the PortManager
so that we get silence on all registered ports.
we overwrite the silence on the two ports used for latency
measurement.
*/
PortManager::cycle_start (nframes);
PortManager::silence (nframes);
if (_latency_input_port && _latency_output_port) {
PortEngine& pe (port_engine());
Sample* in = (Sample*) pe.get_buffer (_latency_input_port, nframes);
Sample* out = (Sample*) pe.get_buffer (_latency_output_port, nframes);
_mtdm->process (nframes, in, out);
}
PortManager::cycle_end (nframes);
return_after_remove_check = true;
} else if (_measuring_latency == MeasureMIDI && _mididm) {
/* run a normal cycle from the perspective of the PortManager
so that we get silence on all registered ports.
we overwrite the silence on the two ports used for latency
measurement.
*/
PortManager::cycle_start (nframes);
PortManager::silence (nframes);
if (_latency_input_port && _latency_output_port) {
PortEngine& pe (port_engine());
_mididm->process (nframes, pe,
pe.get_buffer (_latency_input_port, nframes),
pe.get_buffer (_latency_output_port, nframes));
}
PortManager::cycle_end (nframes);
return_after_remove_check = true;
} else if (_latency_flush_samples) {
/* wait for the appropriate duration for the MTDM signal to
* drain from the ports before we revert to normal behaviour.
*/
PortManager::cycle_start (nframes);
PortManager::silence (nframes);
PortManager::cycle_end (nframes);
if (_latency_flush_samples > nframes) {
_latency_flush_samples -= nframes;
} else {
_latency_flush_samples = 0;
}
return_after_remove_check = true;
}
if (session_remove_pending) {
/* perform the actual session removal */
if (session_removal_countdown < 0) {
/* fade out over 1 second */
session_removal_countdown = sample_rate()/2;
session_removal_gain = GAIN_COEFF_UNITY;
session_removal_gain_step = 1.0/session_removal_countdown;
} else if (session_removal_countdown > 0) {
/* we'll be fading audio out.
if this is the last time we do this as part
of session removal, do a MIDI panic now
to get MIDI stopped. This relies on the fact
that "immediate data" (aka "out of band data") from
MIDI tracks is *appended* after any other data,
so that it emerges after any outbound note ons, etc.
*/
if (session_removal_countdown <= nframes) {
assert (_session);
_session->midi_panic ();
}
} else {
/* fade out done */
_session = 0;
session_removal_countdown = -1; // reset to "not in progress"
session_remove_pending = false;
session_removed.signal(); // wakes up thread that initiated session removal
}
}
if (return_after_remove_check) {
return 0;
}
TransportMasterManager& tmm (TransportMasterManager::instance());
/* make sure the TMM is up to date about the current session */
if (_session != tmm.session()) {
tmm.set_session (_session);
}
if (_session == 0) {
if (!_freewheeling) {
PortManager::silence_outputs (nframes);
}
_processed_samples = next_processed_samples;
return 0;
}
if (!_freewheeling || Freewheel.empty()) {
/* catch_speed is the speed that we estimate we need to run at
to catch (or remain locked to) a transport master.
*/
double catch_speed = tmm.pre_process_transport_masters (nframes, sample_time_at_cycle_start());
catch_speed = _session->plan_master_strategy (nframes, tmm.get_current_speed_in_process_context(), tmm.get_current_position_in_process_context(), catch_speed);
Port::set_speed_ratio (catch_speed);
DEBUG_TRACE (DEBUG::Slave, string_compose ("transport master (current=%1) gives speed %2 (ports using %3)\n", tmm.current() ? tmm.current()->name() : string("[]"), catch_speed, Port::speed_ratio()));
#if 0 // USE FOR DEBUG ONLY
/* use with Dummy backend, engine pulse and
* scripts/_find_nonzero_sample.lua
* to correlate with recorded region alignment.
*/
static bool was_rolling = false;
bool is_rolling = _session->transport_rolling();
if (!was_rolling && is_rolling) {
samplepos_t stacs = sample_time_at_cycle_start ();
samplecnt_t sr = sample_rate ();
samplepos_t tp = _session->transport_sample ();
/* Note: this does not take Port latency into account:
* - always add 12 samples (Port::_resampler_quality)
* - ExistingMaterial: subtract playback latency from engine-pulse
* We assume the player listens and plays along. Recorded region is moved
* back by playback_latency
*/
printf (" ******** Starting play at %ld, next pulse: %ld\n", stacs, ((sr - (stacs % sr)) %sr) + tp);
}
was_rolling = is_rolling;
#endif
}
/* tell all relevant objects that we're starting a new cycle */
InternalSend::CycleStart (nframes);
/* tell all Ports that we're starting a new cycle */
PortManager::cycle_start (nframes, _session);
/* test if we are freewheeling and there are freewheel signals connected.
* ardour should act normally even when freewheeling unless /it/ is
* exporting (which is what Freewheel.empty() tests for).
*/
if (_freewheeling && !Freewheel.empty()) {
Freewheel (nframes);
} else {
if (Port::cycle_nframes () <= nframes) {
_session->process (Port::cycle_nframes ());
} else {
pframes_t remain = Port::cycle_nframes ();
while (remain > 0) {
/* keep track of split_cycle() calls by Session::process */
samplecnt_t poff = Port::port_offset ();
pframes_t nf = std::min (remain, nframes);
_session->process (nf);
remain -= nf;
if (remain > 0) {
/* calculate split-cycle offset */
samplecnt_t delta = Port::port_offset () - poff;
assert (delta >= 0 && delta <= nf);
if (nf > delta) {
split_cycle (nf - delta);
}
}
}
}
}
if (_freewheeling) {
PortManager::cycle_end (nframes, _session);
return 0;
}
if (!_running) {
_processed_samples = next_processed_samples;
return 0;
}
if (last_monitor_check + monitor_check_interval < next_processed_samples) {
PortManager::check_monitoring ();
last_monitor_check = next_processed_samples;
}
#ifdef SILENCE_AFTER_SECONDS
bool was_silent = (_silence_countdown == 0);
if (_silence_countdown >= nframes) {
_silence_countdown -= nframes;
} else {
_silence_countdown = 0;
}
if (!was_silent && _silence_countdown == 0) {
_silence_hit_cnt++;
BecameSilent (); /* EMIT SIGNAL */
}
if (_silence_countdown == 0 || _session->silent()) {
PortManager::silence (nframes);
}
#else
if (_session->silent()) {
PortManager::silence (nframes, _session);
}
#endif
if (session_remove_pending && session_removal_countdown) {
PortManager::cycle_end_fade_out (session_removal_gain, session_removal_gain_step, nframes, _session);
if (session_removal_countdown > nframes) {
session_removal_countdown -= nframes;
} else {
session_removal_countdown = 0;
}
session_removal_gain -= (nframes * session_removal_gain_step);
} else {
PortManager::cycle_end (nframes, _session);
}
_processed_samples = next_processed_samples;
PT_TIMING_CHECK (2);
return 0;
}
void
AudioEngine::reset_silence_countdown ()
{
#ifdef SILENCE_AFTER_SECONDS
double sr = 48000; /* default in case there is no backend */
sr = sample_rate();
_silence_countdown = max (60 * sr, /* 60 seconds */
sr * (SILENCE_AFTER_SECONDS / ::pow (2.0, (double) _silence_hit_cnt)));
#endif
}
void
AudioEngine::launch_device_control_app()
{
if (_state_lock.trylock () ) {
_backend->launch_control_app ();
_state_lock.unlock ();
}
}
void
AudioEngine::request_backend_reset()
{
Glib::Threads::Mutex::Lock guard (_reset_request_lock);
g_atomic_int_inc (&_hw_reset_request_count);
_hw_reset_condition.signal ();
}
int
AudioEngine::backend_reset_requested()
{
return g_atomic_int_get (&_hw_reset_request_count);
}
void
AudioEngine::do_reset_backend()
{
SessionEvent::create_per_thread_pool (X_("Backend reset processing thread"), 1024);
pthread_set_name ("EngineWatchdog");
Glib::Threads::Mutex::Lock guard (_reset_request_lock);
while (!_stop_hw_reset_processing) {
if (g_atomic_int_get (&_hw_reset_request_count) != 0 && _backend) {
_reset_request_lock.unlock();
Glib::Threads::RecMutex::Lock pl (_state_lock);
g_atomic_int_dec_and_test (&_hw_reset_request_count);
std::cout << "AudioEngine::RESET::Reset request processing. Requests left: " << _hw_reset_request_count << std::endl;
DeviceResetStarted(); // notify about device reset to be started
// backup the device name
std::string name = _backend->device_name ();
std::cout << "AudioEngine::RESET::Reseting device..." << std::endl;
if ( ( 0 == stop () ) &&
( 0 == _backend->reset_device () ) &&
( 0 == start () ) ) {
std::cout << "AudioEngine::RESET::Engine started..." << std::endl;
// inform about possible changes
BufferSizeChanged (_backend->buffer_size() );
DeviceResetFinished(); // notify about device reset finish
} else {
DeviceResetFinished(); // notify about device reset finish
// we've got an error
DeviceError();
}
std::cout << "AudioEngine::RESET::Done." << std::endl;
_reset_request_lock.lock();
} else {
_hw_reset_condition.wait (_reset_request_lock);
}
}
}
void
AudioEngine::request_device_list_update()
{
Glib::Threads::Mutex::Lock guard (_devicelist_update_lock);
g_atomic_int_inc (&_hw_devicelist_update_count);
_hw_devicelist_update_condition.signal ();
}
void
AudioEngine::do_devicelist_update()
{
SessionEvent::create_per_thread_pool (X_("Device list update processing thread"), 512);
pthread_set_name ("DeviceList");
Glib::Threads::Mutex::Lock guard (_devicelist_update_lock);
while (!_stop_hw_devicelist_processing) {
if (g_atomic_int_get (&_hw_devicelist_update_count)) {
_devicelist_update_lock.unlock();
Glib::Threads::RecMutex::Lock pl (_state_lock);
g_atomic_int_dec_and_test (&_hw_devicelist_update_count);
DeviceListChanged (); /* EMIT SIGNAL */
_devicelist_update_lock.lock();
} else {
_hw_devicelist_update_condition.wait (_devicelist_update_lock);
}
}
}
void
AudioEngine::start_hw_event_processing()
{
if (_hw_reset_event_thread == 0) {
g_atomic_int_set(&_hw_reset_request_count, 0);
g_atomic_int_set(&_stop_hw_reset_processing, 0);
_hw_reset_event_thread = Glib::Threads::Thread::create (boost::bind (&AudioEngine::do_reset_backend, this));
}
if (_hw_devicelist_update_thread == 0) {
g_atomic_int_set(&_hw_devicelist_update_count, 0);
g_atomic_int_set(&_stop_hw_devicelist_processing, 0);
_hw_devicelist_update_thread = Glib::Threads::Thread::create (boost::bind (&AudioEngine::do_devicelist_update, this));
}
}
void
AudioEngine::stop_hw_event_processing()
{
if (_hw_reset_event_thread) {
g_atomic_int_set(&_stop_hw_reset_processing, 1);
g_atomic_int_set(&_hw_reset_request_count, 0);
_hw_reset_condition.signal ();
_hw_reset_event_thread->join ();
_hw_reset_event_thread = 0;
}
if (_hw_devicelist_update_thread) {
g_atomic_int_set(&_stop_hw_devicelist_processing, 1);
g_atomic_int_set(&_hw_devicelist_update_count, 0);
_hw_devicelist_update_condition.signal ();
_hw_devicelist_update_thread->join ();
_hw_devicelist_update_thread = 0;
}
}
void
AudioEngine::set_session (Session *s)
{
Glib::Threads::Mutex::Lock pl (_process_lock);
SessionHandlePtr::set_session (s);
if (_session) {
_init_countdown = std::max (4, (int)(_backend->sample_rate () / _backend->buffer_size ()) / 8);
}
}
void
AudioEngine::remove_session ()
{
Glib::Threads::Mutex::Lock lm (_process_lock);
if (_running) {
if (_session) {
session_remove_pending = true;
/* signal the start of the fade out countdown */
session_removal_countdown = -1;
session_removed.wait(_process_lock);
}
} else {
SessionHandlePtr::set_session (0);
}
remove_all_ports ();
}
void
AudioEngine::died ()
{
/* called from a signal handler for SIGPIPE */
_running = false;
}
int
AudioEngine::reset_timebase ()
{
if (_session) {
if (_session->config.get_jack_time_master()) {
_backend->set_time_master (true);
} else {
_backend->set_time_master (false);
}
}
return 0;
}
void
AudioEngine::destroy ()
{
delete _instance;
_instance = 0;
}
int
AudioEngine::discover_backends ()
{
vector<std::string> backend_modules;
_backends.clear ();
Glib::PatternSpec so_extension_pattern("*backend.so");
Glib::PatternSpec dylib_extension_pattern("*backend.dylib");
#if defined(PLATFORM_WINDOWS) && defined(DEBUGGABLE_BACKENDS)
#if defined(DEBUG) || defined(_DEBUG)
Glib::PatternSpec dll_extension_pattern("*backendD.dll");
#else
Glib::PatternSpec dll_extension_pattern("*backendRDC.dll");
#endif
#else
Glib::PatternSpec dll_extension_pattern("*backend.dll");
#endif
find_files_matching_pattern (backend_modules, backend_search_path (),
so_extension_pattern);
find_files_matching_pattern (backend_modules, backend_search_path (),
dylib_extension_pattern);
find_files_matching_pattern (backend_modules, backend_search_path (),
dll_extension_pattern);
DEBUG_TRACE (DEBUG::AudioEngine, string_compose ("looking for backends in %1\n", backend_search_path().to_string()));
for (vector<std::string>::iterator i = backend_modules.begin(); i != backend_modules.end(); ++i) {
AudioBackendInfo* info;
DEBUG_TRACE (DEBUG::AudioEngine, string_compose ("Checking possible backend in %1\n", *i));
if ((info = backend_discover (*i)) != 0) {
_backends.insert (make_pair (info->name, info));
}
}
DEBUG_TRACE (DEBUG::AudioEngine, string_compose ("Found %1 backends\n", _backends.size()));
return _backends.size();
}
AudioBackendInfo*
AudioEngine::backend_discover (const string& path)
{
#ifdef PLATFORM_WINDOWS
// do not show popup dialog (e.g. missing libjack.dll)
// win7+ should use SetThreadErrorMode()
SetErrorMode(SEM_FAILCRITICALERRORS);
#endif
Glib::Module module (path);
#ifdef PLATFORM_WINDOWS
SetErrorMode(0); // reset to system default
#endif
AudioBackendInfo* info;
AudioBackendInfo* (*dfunc)(void);
void* func = 0;
if (!module) {
error << string_compose(_("AudioEngine: cannot load module \"%1\" (%2)"), path,
Glib::Module::get_last_error()) << endmsg;
return 0;
}
if (!module.get_symbol ("descriptor", func)) {
error << string_compose(_("AudioEngine: backend at \"%1\" has no descriptor function."), path) << endmsg;
error << Glib::Module::get_last_error() << endmsg;
return 0;
}
dfunc = (AudioBackendInfo* (*)(void))func;
info = dfunc();
if (!info->available()) {
return 0;
}
module.make_resident ();
return info;
}
#ifdef NDEBUG
static bool running_from_source_tree ()
{
// dup ARDOUR_UI_UTILS::running_from_source_tree ()
gchar const *x = g_getenv ("ARDOUR_THEMES_PATH");
return x && (string (x).find ("gtk2_ardour") != string::npos);
}
#endif
vector<const AudioBackendInfo*>
AudioEngine::available_backends() const
{
vector<const AudioBackendInfo*> r;
for (BackendMap::const_iterator i = _backends.begin(); i != _backends.end(); ++i) {
#ifdef NDEBUG
if (i->first == "None (Dummy)" && !running_from_source_tree () && Config->get_hide_dummy_backend ()) {
continue;
}
#endif
r.push_back (i->second);
}
return r;
}
string
AudioEngine::current_backend_name() const
{
if (_backend) {
return _backend->name();
}
return string();
}
void
AudioEngine::drop_backend ()
{
if (_backend) {
/* see also ::stop() */
_backend->stop ();
_running = false;
if (_session && !_session->loading() && !_session->deletion_in_progress()) {
// it's not a halt, but should be handled the same way:
// disable record, stop transport and I/O processign but save the data.
_session->engine_halted ();
}
Port::PortDrop (); /* EMIT SIGNAL */
TransportMasterManager& tmm (TransportMasterManager::instance());
tmm.engine_stopped ();
/* Stopped is needed for Graph to explicitly terminate threads */
Stopped (); /* EMIT SIGNAL */
_backend->drop_device ();
_backend.reset ();
}
}
boost::shared_ptr<AudioBackend>
AudioEngine::set_backend (const std::string& name, const std::string& arg1, const std::string& arg2)
{
BackendMap::iterator b = _backends.find (name);
if (b == _backends.end()) {
return boost::shared_ptr<AudioBackend>();
}
drop_backend ();
try {
if (b->second->instantiate (arg1, arg2)) {
throw failed_constructor ();
}
_backend = b->second->factory (*this);
} catch (exception& e) {
error << string_compose (_("Could not create backend for %1: %2"), name, e.what()) << endmsg;
return boost::shared_ptr<AudioBackend>();
}
return _backend;
}
/* BACKEND PROXY WRAPPERS */
int
AudioEngine::start (bool for_latency)
{
if (!_backend) {
return -1;
}
if (_running && _backend->can_change_systemic_latency_when_running()) {
_started_for_latency = for_latency;
}
if (_running) {
return 0;
}
_processed_samples = 0;
last_monitor_check = 0;
int error_code = _backend->start (for_latency);
if (error_code != 0) {
_last_backend_error_string = AudioBackend::get_error_string((AudioBackend::ErrorCode) error_code);
return -1;
}
_running = true;
if (_session) {
_session->set_sample_rate (_backend->sample_rate());
if (_session->config.get_jack_time_master()) {
_backend->set_time_master (true);
}
}
midi_info_dirty = true;
if (!for_latency) {
/* Call the library-wide ::init_post_engine() before emitting
* running to ensure that its tasks are complete before any
* signal handlers execute. PBD::Signal does not ensure
* ordering of signal handlers so even if ::init_post_engine()
* is connected first, it may not run first.
*/
ARDOUR::init_post_engine (_start_cnt);
Running (_start_cnt); /* EMIT SIGNAL */
/* latency start/stop cycles do not count as "starts" */
_start_cnt++;
}
return 0;
}
int
AudioEngine::stop (bool for_latency)
{
bool stop_engine = true;
if (!_backend) {
return 0;
}
Glib::Threads::Mutex::Lock pl (_process_lock, Glib::Threads::NOT_LOCK);
if (running()) {
pl.acquire ();
}
if (for_latency && _backend->can_change_systemic_latency_when_running()) {
stop_engine = false;
if (_running && _started_for_latency) {
_backend->start (false); // keep running, reload latencies
}
} else {
if (_backend->stop ()) {
if (pl.locked ()) {
pl.release ();
}
return -1;
}
}
if (pl.locked ()) {
pl.release ();
}
const bool was_running_will_stop = (_running && stop_engine);
if (was_running_will_stop) {
_running = false;
}
if (_session && was_running_will_stop && !_session->loading() && !_session->deletion_in_progress()) {
// it's not a halt, but should be handled the same way:
// disable record, stop transport and I/O processign but save the data.
_session->engine_halted ();
}
if (was_running_will_stop) {
if (!for_latency) {
_started_for_latency = false;
} else if (!_started_for_latency) {
_stopped_for_latency = true;
}
}
_processed_samples = 0;
_measuring_latency = MeasureNone;
_latency_output_port.reset ();
_latency_input_port.reset ();
if (stop_engine) {
Port::PortDrop ();
}
if (stop_engine) {
TransportMasterManager& tmm (TransportMasterManager::instance());
tmm.engine_stopped ();
Stopped (); /* EMIT SIGNAL */
}
return 0;
}
int
AudioEngine::freewheel (bool start_stop)
{
if (!_backend) {
return -1;
}
/* _freewheeling will be set when first Freewheel signal occurs */
return _backend->freewheel (start_stop);
}
float
AudioEngine::get_dsp_load() const
{
if (!_backend || !_running) {
return 0.0;
}
return _backend->dsp_load ();
}
bool
AudioEngine::is_realtime() const
{
if (!_backend) {
return false;
}
return _backend->is_realtime();
}
void
AudioEngine::transport_start ()
{
if (!_backend) {
return;
}
return _backend->transport_start ();
}
void
AudioEngine::transport_stop ()
{
if (!_backend) {
return;
}
return _backend->transport_stop ();
}
TransportState
AudioEngine::transport_state ()
{
if (!_backend) {
return TransportStopped;
}
return _backend->transport_state ();
}
void
AudioEngine::transport_locate (samplepos_t pos)
{
if (!_backend) {
return;
}
return _backend->transport_locate (pos);
}
samplepos_t
AudioEngine::transport_sample()
{
if (!_backend) {
return 0;
}
return _backend->transport_sample ();
}
samplecnt_t
AudioEngine::sample_rate () const
{
if (!_backend) {
return 0;
}
return _backend->sample_rate ();
}
pframes_t
AudioEngine::samples_per_cycle () const
{
if (!_backend) {
return 0;
}
return _backend->buffer_size ();
}
int
AudioEngine::usecs_per_cycle () const
{
if (!_backend) {
return -1;
}
return _backend->usecs_per_cycle ();
}
size_t
AudioEngine::raw_buffer_size (DataType t)
{
if (!_backend) {
return -1;
}
return _backend->raw_buffer_size (t);
}
samplepos_t
AudioEngine::sample_time ()
{
if (!_backend) {
return 0;
}
return _backend->sample_time ();
}
samplepos_t
AudioEngine::sample_time_at_cycle_start ()
{
if (!_backend) {
return 0;
}
return _backend->sample_time_at_cycle_start ();
}
pframes_t
AudioEngine::samples_since_cycle_start ()
{
if (!_backend) {
return 0;
}
return _backend->samples_since_cycle_start ();
}
bool
AudioEngine::get_sync_offset (pframes_t& offset) const
{
if (!_backend) {
return false;
}
return _backend->get_sync_offset (offset);
}
int
AudioEngine::create_process_thread (boost::function<void()> func)
{
if (!_backend) {
return -1;
}
return _backend->create_process_thread (func);
}
int
AudioEngine::join_process_threads ()
{
if (!_backend) {
return -1;
}
return _backend->join_process_threads ();
}
bool
AudioEngine::in_process_thread ()
{
if (!_backend) {
return false;
}
return _backend->in_process_thread ();
}
uint32_t
AudioEngine::process_thread_count ()
{
if (!_backend) {
return 0;
}
return _backend->process_thread_count ();
}
int
AudioEngine::set_device_name (const std::string& name)
{
if (!_backend) {
return -1;
}
return _backend->set_device_name (name);
}
int
AudioEngine::set_sample_rate (float sr)
{
if (!_backend) {
return -1;
}
return _backend->set_sample_rate (sr);
}
int
AudioEngine::set_buffer_size (uint32_t bufsiz)
{
if (!_backend) {
return -1;
}
return _backend->set_buffer_size (bufsiz);
}
int
AudioEngine::set_interleaved (bool yn)
{
if (!_backend) {
return -1;
}
return _backend->set_interleaved (yn);
}
int
AudioEngine::set_input_channels (uint32_t ic)
{
if (!_backend) {
return -1;
}
return _backend->set_input_channels (ic);
}
int
AudioEngine::set_output_channels (uint32_t oc)
{
if (!_backend) {
return -1;
}
return _backend->set_output_channels (oc);
}
int
AudioEngine::set_systemic_input_latency (uint32_t il)
{
if (!_backend) {
return -1;
}
return _backend->set_systemic_input_latency (il);
}
int
AudioEngine::set_systemic_output_latency (uint32_t ol)
{
if (!_backend) {
return -1;
}
return _backend->set_systemic_output_latency (ol);
}
bool
AudioEngine::thread_initialised_for_audio_processing ()
{
return SessionEvent::has_per_thread_pool () && AsyncMIDIPort::is_process_thread();
}
/* END OF BACKEND PROXY API */
void
AudioEngine::thread_init_callback (void* arg)
{
/* make sure that anybody who needs to know about this thread
knows about it.
*/
pthread_set_name (X_("audioengine"));
const int thread_num = g_atomic_int_add (&audioengine_thread_cnt, 1);
const string thread_name = string_compose (X_("AudioEngine %1"), thread_num);
SessionEvent::create_per_thread_pool (thread_name, 512);
PBD::notify_event_loops_about_thread_creation (pthread_self(), thread_name, 4096);
AsyncMIDIPort::set_process_thread (pthread_self());
if (arg) {
delete AudioEngine::instance()->_main_thread;
/* the special thread created/managed by the backend */
AudioEngine::instance()->_main_thread = new ProcessThread;
}
}
int
AudioEngine::sync_callback (TransportState state, samplepos_t position)
{
DEBUG_TRACE (DEBUG::BackendCallbacks, string_compose (X_("sync callback %1, %2\n"), state, position));
if (_session) {
return _session->backend_sync_callback (state, position);
}
return 0;
}
void
AudioEngine::freewheel_callback (bool onoff)
{
DEBUG_TRACE (DEBUG::BackendCallbacks, string_compose (X_("freewheel callback onoff %1\n"), onoff));
_freewheeling = onoff;
}
void
AudioEngine::latency_callback (bool for_playback)
{
DEBUG_TRACE (DEBUG::BackendCallbacks, string_compose (X_("latency callback playback ? %1\n"), for_playback));
if (_session) {
_session->update_latency (for_playback);
}
}
void
AudioEngine::update_latencies ()
{
if (_backend) {
_backend->update_latencies ();
}
}
void
AudioEngine::halted_callback (const char* why)
{
DEBUG_TRACE (DEBUG::BackendCallbacks, string_compose (X_("halted callback why: [%1]\n"), why));
if (_in_destructor) {
/* everything is under control */
return;
}
_running = false;
Port::PortDrop (); /* EMIT SIGNAL */
if (!_started_for_latency) {
Halted (why); /* EMIT SIGNAL */
}
}
bool
AudioEngine::setup_required () const
{
if (_backend) {
if (_backend->info().already_configured())
return false;
} else {
if (_backends.size() == 1 && _backends.begin()->second->already_configured()) {
return false;
}
}
return true;
}
int
AudioEngine::prepare_for_latency_measurement ()
{
if (!_backend) {
return -1;
}
if (running() && _started_for_latency) {
return 0;
}
if (_backend->can_change_systemic_latency_when_running()) {
if (_running) {
_backend->start (true); // zero latency reporting of running backend
} else if (start (true)) {
return -1;
}
_started_for_latency = true;
return 0;
}
if (running()) {
stop (true);
}
if (start (true)) {
return -1;
}
_started_for_latency = true;
return 0;
}
int
AudioEngine::start_latency_detection (bool for_midi)
{
if (prepare_for_latency_measurement ()) {
return -1;
}
PortEngine& pe (port_engine());
delete _mtdm;
_mtdm = 0;
delete _mididm;
_mididm = 0;
/* find the ports we will connect to */
PortEngine::PortHandle out = pe.get_port_by_name (_latency_output_name);
PortEngine::PortHandle in = pe.get_port_by_name (_latency_input_name);
if (!out || !in) {
stop (true);
return -1;
}
/* create the ports we will use to read/write data */
if (for_midi) {
if ((_latency_output_port = pe.register_port ("latency_out", DataType::MIDI, IsOutput)) == 0) {
stop (true);
return -1;
}
if (pe.connect (_latency_output_port, _latency_output_name)) {
pe.unregister_port (_latency_output_port);
stop (true);
return -1;
}
const string portname ("latency_in");
if ((_latency_input_port = pe.register_port (portname, DataType::MIDI, IsInput)) == 0) {
pe.unregister_port (_latency_input_port);
pe.unregister_port (_latency_output_port);
stop (true);
return -1;
}
if (pe.connect (_latency_input_name, make_port_name_non_relative (portname))) {
pe.unregister_port (_latency_input_port);
pe.unregister_port (_latency_output_port);
stop (true);
return -1;
}
_mididm = new MIDIDM (sample_rate());
} else {
if ((_latency_output_port = pe.register_port ("latency_out", DataType::AUDIO, IsOutput)) == 0) {
stop (true);
return -1;
}
if (pe.connect (_latency_output_port, _latency_output_name)) {
pe.unregister_port (_latency_output_port);
stop (true);
return -1;
}
const string portname ("latency_in");
if ((_latency_input_port = pe.register_port (portname, DataType::AUDIO, IsInput)) == 0) {
pe.unregister_port (_latency_input_port);
pe.unregister_port (_latency_output_port);
stop (true);
return -1;
}
if (pe.connect (_latency_input_name, make_port_name_non_relative (portname))) {
pe.unregister_port (_latency_input_port);
pe.unregister_port (_latency_output_port);
stop (true);
return -1;
}
_mtdm = new MTDM (sample_rate());
}
LatencyRange lr;
_latency_signal_latency = 0;
lr = pe.get_latency_range (in, false);
_latency_signal_latency = lr.max;
lr = pe.get_latency_range (out, true);
_latency_signal_latency += lr.max;
/* all created and connected, lets go */
_latency_flush_samples = samples_per_cycle();
_measuring_latency = for_midi ? MeasureMIDI : MeasureAudio;
return 0;
}
void
AudioEngine::stop_latency_detection ()
{
_measuring_latency = MeasureNone;
if (_latency_output_port) {
port_engine().unregister_port (_latency_output_port);
_latency_output_port.reset();
}
if (_latency_input_port) {
port_engine().unregister_port (_latency_input_port);
_latency_input_port.reset();
}
if (_running && _backend->can_change_systemic_latency_when_running()) {
if (_started_for_latency) {
_running = false; // force reload: reset latencies and emit Running()
start ();
}
}
if (_running && !_started_for_latency) {
assert (!_stopped_for_latency);
return;
}
if (!_backend->can_change_systemic_latency_when_running()) {
stop (true);
}
if (_stopped_for_latency) {
start ();
}
_stopped_for_latency = false;
_started_for_latency = false;
}
void
AudioEngine::set_latency_output_port (const string& name)
{
_latency_output_name = name;
}
void
AudioEngine::set_latency_input_port (const string& name)
{
_latency_input_name = name;
}
void
AudioEngine::add_pending_port_deletion (Port* p)
{
if (_session) {
DEBUG_TRACE (DEBUG::Ports, string_compose ("adding %1 to pending port deletion list\n", p->name()));
if (_port_deletions_pending.write (&p, 1) != 1) {
error << string_compose (_("programming error: port %1 could not be placed on the pending deletion queue\n"), p->name()) << endmsg;
}
_session->auto_connect_thread_wakeup ();
} else {
DEBUG_TRACE (DEBUG::Ports, string_compose ("Directly delete port %1\n", p->name()));
delete p;
}
}