mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-29 01:47:43 +01:00
This new design will work even when threads that need to receive messages from RT threads are created *after* the RT threads. The existing design would fail because the RT thread(s) would never be known the later created threads, and so signals emitted by the RT thread and causing call_slot() in the receiver would end up being enqueued using a lock-protected list. The new design ensures that communication always uses a lock-free FIFO instead
1464 lines
31 KiB
C++
1464 lines
31 KiB
C++
/*
|
|
Copyright (C) 2002 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 <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/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/session.h"
|
|
|
|
#include "i18n.h"
|
|
|
|
using namespace std;
|
|
using namespace ARDOUR;
|
|
using namespace PBD;
|
|
|
|
AudioEngine* AudioEngine::_instance = 0;
|
|
|
|
#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_frames (0)
|
|
, m_meter_thread (0)
|
|
, _main_thread (0)
|
|
, _mtdm (0)
|
|
, _mididm (0)
|
|
, _measuring_latency (MeasureNone)
|
|
, _latency_input_port (0)
|
|
, _latency_output_port (0)
|
|
, _latency_flush_frames (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::ErrorCode)-1))
|
|
, _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)
|
|
#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();
|
|
}
|
|
}
|
|
|
|
AudioEngine*
|
|
AudioEngine::create ()
|
|
{
|
|
if (_instance) {
|
|
return _instance;
|
|
}
|
|
|
|
_instance = new AudioEngine ();
|
|
|
|
return _instance;
|
|
}
|
|
|
|
void
|
|
AudioEngine::split_cycle (pframes_t offset)
|
|
{
|
|
/* caller must hold process lock */
|
|
|
|
Port::increment_global_port_buffer_offset (offset);
|
|
|
|
/* tell all Ports that we're going to start a new (split) cycle */
|
|
|
|
boost::shared_ptr<Ports> p = ports.reader();
|
|
|
|
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_frame_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)
|
|
{
|
|
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 frames 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);
|
|
|
|
PT_TIMING_REF;
|
|
PT_TIMING_CHECK (1);
|
|
|
|
/// The number of frames that will have been processed when we've finished
|
|
pframes_t next_processed_frames;
|
|
|
|
/* handle wrap around of total frames counter */
|
|
|
|
if (max_framepos - _processed_frames < nframes) {
|
|
next_processed_frames = nframes - (max_framepos - _processed_frames);
|
|
} else {
|
|
next_processed_frames = _processed_frames + 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);
|
|
}
|
|
|
|
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_frames) {
|
|
|
|
/* 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_frames > nframes) {
|
|
_latency_flush_frames -= nframes;
|
|
} else {
|
|
_latency_flush_frames = 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) {
|
|
_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;
|
|
}
|
|
|
|
if (_session == 0) {
|
|
|
|
if (!_freewheeling) {
|
|
PortManager::cycle_start (nframes);
|
|
PortManager::cycle_end (nframes);
|
|
}
|
|
|
|
_processed_frames = next_processed_frames;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
/* 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 {
|
|
_session->process (nframes);
|
|
}
|
|
|
|
if (_freewheeling) {
|
|
PortManager::cycle_end (nframes);
|
|
return 0;
|
|
}
|
|
|
|
if (!_running) {
|
|
_processed_frames = next_processed_frames;
|
|
return 0;
|
|
}
|
|
|
|
if (last_monitor_check + monitor_check_interval < next_processed_frames) {
|
|
|
|
PortManager::check_monitoring ();
|
|
last_monitor_check = next_processed_frames;
|
|
}
|
|
|
|
#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);
|
|
}
|
|
#endif
|
|
|
|
if (session_remove_pending && session_removal_countdown) {
|
|
|
|
PortManager::fade_out (session_removal_gain, session_removal_gain_step, nframes);
|
|
|
|
if (session_removal_countdown > nframes) {
|
|
session_removal_countdown -= nframes;
|
|
} else {
|
|
session_removal_countdown = 0;
|
|
}
|
|
|
|
session_removal_gain -= (nframes * session_removal_gain_step);
|
|
}
|
|
|
|
PortManager::cycle_end (nframes);
|
|
|
|
_processed_frames = next_processed_frames;
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
Glib::Threads::Mutex::Lock guard (_devicelist_update_lock);
|
|
|
|
while (!_stop_hw_devicelist_processing) {
|
|
|
|
if (_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) {
|
|
|
|
pframes_t blocksize = samples_per_cycle ();
|
|
|
|
PortManager::cycle_start (blocksize);
|
|
|
|
_session->process (blocksize);
|
|
_session->process (blocksize);
|
|
_session->process (blocksize);
|
|
_session->process (blocksize);
|
|
_session->process (blocksize);
|
|
_session->process (blocksize);
|
|
_session->process (blocksize);
|
|
_session->process (blocksize);
|
|
|
|
PortManager::cycle_end (blocksize);
|
|
}
|
|
}
|
|
|
|
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::reconnect_session_routes (bool reconnect_inputs, bool reconnect_outputs)
|
|
{
|
|
#ifdef USE_TRACKS_CODE_FEATURES
|
|
if (_session) {
|
|
_session->reconnect_existing_routes(true, true, reconnect_inputs, reconnect_outputs);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
vector<const AudioBackendInfo*>
|
|
AudioEngine::available_backends() const
|
|
{
|
|
vector<const AudioBackendInfo*> r;
|
|
|
|
for (BackendMap::const_iterator i = _backends.begin(); i != _backends.end(); ++i) {
|
|
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) {
|
|
_backend->stop ();
|
|
// Stopped is needed for Graph to explicitly terminate threads
|
|
Stopped (); /* EMIT SIGNAL */
|
|
_backend->drop_device ();
|
|
_backend.reset ();
|
|
_running = false;
|
|
}
|
|
}
|
|
|
|
boost::shared_ptr<AudioBackend>
|
|
AudioEngine::set_default_backend ()
|
|
{
|
|
if (_backends.empty()) {
|
|
return boost::shared_ptr<AudioBackend>();
|
|
}
|
|
|
|
return set_backend (_backends.begin()->first, "", "");
|
|
}
|
|
|
|
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) {
|
|
return 0;
|
|
}
|
|
|
|
_processed_frames = 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_frame_rate (_backend->sample_rate());
|
|
|
|
if (_session->config.get_jack_time_master()) {
|
|
_backend->set_time_master (true);
|
|
}
|
|
|
|
}
|
|
|
|
if (!for_latency) {
|
|
Running(); /* EMIT SIGNAL */
|
|
}
|
|
|
|
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;
|
|
} else {
|
|
if (_backend->stop ()) {
|
|
pl.release ();
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (pl.locked ()) {
|
|
pl.release ();
|
|
}
|
|
|
|
if (_session && _running && stop_engine &&
|
|
(_session->state_of_the_state() & Session::Loading) == 0 &&
|
|
(_session->state_of_the_state() & Session::Deletion) == 0) {
|
|
// 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 (stop_engine) {
|
|
_running = false;
|
|
}
|
|
_processed_frames = 0;
|
|
_measuring_latency = MeasureNone;
|
|
_latency_output_port = 0;
|
|
_latency_input_port = 0;
|
|
_started_for_latency = false;
|
|
|
|
if (stop_engine) {
|
|
Port::PortDrop ();
|
|
}
|
|
|
|
if (!for_latency && stop_engine) {
|
|
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();
|
|
}
|
|
|
|
bool
|
|
AudioEngine::connected() const
|
|
{
|
|
if (!_backend) {
|
|
return false;
|
|
}
|
|
|
|
return _backend->available();
|
|
}
|
|
|
|
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 (framepos_t pos)
|
|
{
|
|
if (!_backend) {
|
|
return;
|
|
}
|
|
return _backend->transport_locate (pos);
|
|
}
|
|
|
|
framepos_t
|
|
AudioEngine::transport_frame()
|
|
{
|
|
if (!_backend) {
|
|
return 0;
|
|
}
|
|
return _backend->transport_frame ();
|
|
}
|
|
|
|
framecnt_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);
|
|
}
|
|
|
|
framepos_t
|
|
AudioEngine::sample_time ()
|
|
{
|
|
if (!_backend) {
|
|
return 0;
|
|
}
|
|
return _backend->sample_time ();
|
|
}
|
|
|
|
framepos_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"));
|
|
|
|
SessionEvent::create_per_thread_pool (X_("AudioEngine"), 512);
|
|
|
|
PBD::notify_event_loops_about_thread_creation (pthread_self(), X_("AudioEngine"), 4096);
|
|
|
|
AsyncMIDIPort::set_process_thread (pthread_self());
|
|
|
|
if (arg) {
|
|
/* the special thread created/managed by the backend */
|
|
AudioEngine::instance()->_main_thread = new ProcessThread;
|
|
}
|
|
}
|
|
|
|
int
|
|
AudioEngine::sync_callback (TransportState state, framepos_t position)
|
|
{
|
|
if (_session) {
|
|
return _session->backend_sync_callback (state, position);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
AudioEngine::freewheel_callback (bool onoff)
|
|
{
|
|
_freewheeling = onoff;
|
|
}
|
|
|
|
void
|
|
AudioEngine::latency_callback (bool 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)
|
|
{
|
|
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 (_backend->can_change_systemic_latency_when_running()) {
|
|
if (start()) {
|
|
return -1;
|
|
}
|
|
_backend->set_systemic_input_latency (0);
|
|
_backend->set_systemic_output_latency (0);
|
|
return 0;
|
|
}
|
|
|
|
if (running()) {
|
|
_stopped_for_latency = true;
|
|
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_frames = 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 = 0;
|
|
}
|
|
if (_latency_input_port) {
|
|
port_engine().unregister_port (_latency_input_port);
|
|
_latency_input_port = 0;
|
|
}
|
|
|
|
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;
|
|
}
|