From f1fd7f6fa47f0b86a7097a32e03129102ae0611a Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Mon, 17 Jan 2011 17:53:34 +0000 Subject: some new source git-svn-id: svn://localhost/ardour2/branches/3.0@8523 d708f5d6-7413-0410-9779-e7cbd77b26cf --- libs/ardour/pannable.cc | 97 ++++++++++++ libs/ardour/panner_manager.cc | 163 ++++++++++++++++++++ libs/ardour/panner_search_path.cc | 52 +++++++ libs/ardour/panner_shell.cc | 311 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 623 insertions(+) create mode 100644 libs/ardour/pannable.cc create mode 100644 libs/ardour/panner_manager.cc create mode 100644 libs/ardour/panner_search_path.cc create mode 100644 libs/ardour/panner_shell.cc diff --git a/libs/ardour/pannable.cc b/libs/ardour/pannable.cc new file mode 100644 index 0000000000..e2f8ccc30e --- /dev/null +++ b/libs/ardour/pannable.cc @@ -0,0 +1,97 @@ +#include "ardour/automation_control.h" +#include "ardour/automation_list.h" +#include "ardour/pannable.h" +#include "ardour/session.h" + +using namespace ARDOUR; + +Pannable::Pannable (Session& s) + : Automatable (s) + , SessionHandleRef (s) + , pan_azimuth_control (new AutomationControl (s, PanAzimuthAutomation, + boost::shared_ptr(new AutomationList(PanAzimuthAutomation)), "")) + , pan_elevation_control (new AutomationControl (s, PanElevationAutomation, + boost::shared_ptr(new AutomationList(PanElevationAutomation)), "")) + , pan_width_control (new AutomationControl (s, PanWidthAutomation, + boost::shared_ptr(new AutomationList(PanWidthAutomation)), "")) + , pan_frontback_control (new AutomationControl (s, PanFrontBackAutomation, + boost::shared_ptr(new AutomationList(PanFrontBackAutomation)), "")) + , pan_lfe_control (new AutomationControl (s, PanLFEAutomation, + boost::shared_ptr(new AutomationList(PanLFEAutomation)), "")) + , _auto_state (Off) + , _auto_style (Absolute) +{ + add_control (pan_azimuth_control); + add_control (pan_elevation_control); + add_control (pan_width_control); + add_control (pan_frontback_control); + add_control (pan_lfe_control); +} + +void +Pannable::set_automation_state (AutoState state) +{ + if (state != _auto_state) { + _auto_state = state; + + const Controls& c (controls()); + + for (Controls::const_iterator ci = c.begin(); ci != c.end(); ++ci) { + boost::shared_ptr ac = boost::dynamic_pointer_cast(ci->second); + if (ac) { + ac->alist()->set_automation_state (state); + } + } + + session().set_dirty (); + automation_state_changed (_auto_state); + } +} + +void +Pannable::set_automation_style (AutoStyle style) +{ + if (style != _auto_style) { + _auto_style = style; + + const Controls& c (controls()); + + for (Controls::const_iterator ci = c.begin(); ci != c.end(); ++ci) { + boost::shared_ptr ac = boost::dynamic_pointer_cast(ci->second); + if (ac) { + ac->alist()->set_automation_style (style); + } + } + + session().set_dirty (); + automation_style_changed (); + } +} + +void +Pannable::start_touch (double when) +{ + const Controls& c (controls()); + + for (Controls::const_iterator ci = c.begin(); ci != c.end(); ++ci) { + boost::shared_ptr ac = boost::dynamic_pointer_cast(ci->second); + if (ac) { + ac->alist()->start_touch (when); + } + } + g_atomic_int_set (&_touching, 1); +} + +void +Pannable::stop_touch (bool mark, double when) +{ + const Controls& c (controls()); + + for (Controls::const_iterator ci = c.begin(); ci != c.end(); ++ci) { + boost::shared_ptr ac = boost::dynamic_pointer_cast(ci->second); + if (ac) { + ac->alist()->stop_touch (mark, when); + } + } + g_atomic_int_set (&_touching, 0); +} diff --git a/libs/ardour/panner_manager.cc b/libs/ardour/panner_manager.cc new file mode 100644 index 0000000000..ce7caf15fb --- /dev/null +++ b/libs/ardour/panner_manager.cc @@ -0,0 +1,163 @@ +#include + +#include "pbd/error.h" +#include "pbd/compose.h" +#include "pbd/file_utils.h" + +#include "ardour/panner_manager.h" +#include "ardour/panner_search_path.h" + +using namespace std; +using namespace ARDOUR; +using namespace PBD; + +PannerManager* PannerManager::_instance = 0; + +PannerManager::PannerManager () +{ +} + +PannerManager::~PannerManager () +{ + for (list::iterator p = panner_info.begin(); p != panner_info.end(); ++p) { + delete *p; + } +} + +PannerManager& +PannerManager::instance () +{ + if (_instance == 0) { + _instance = new PannerManager (); + } + + return *_instance; +} + +void +PannerManager::discover_panners () +{ + vector panner_modules; + + Glib::PatternSpec so_extension_pattern("*.so"); + Glib::PatternSpec dylib_extension_pattern("*.dylib"); + + find_matching_files_in_search_path (panner_search_path (), + so_extension_pattern, panner_modules); + + find_matching_files_in_search_path (panner_search_path (), + dylib_extension_pattern, panner_modules); + + info << string_compose (_("looking for panners in %1"), panner_search_path().to_string()) << endmsg; + + for (vector::iterator i = panner_modules.begin(); i != panner_modules.end(); ++i) { + panner_discover ((*i).to_string()); + } +} +int +PannerManager::panner_discover (string path) +{ + PannerInfo* pinfo; + + if ((pinfo = get_descriptor (path)) != 0) { + panner_info.push_back (pinfo); + info << string_compose(_("Panner discovered: \"%1\""), pinfo->descriptor.name) << endmsg; + } + + return 0; +} + +PannerInfo* +PannerManager::get_descriptor (string path) +{ + void *module; + PannerInfo* info = 0; + PanPluginDescriptor *descriptor = 0; + PanPluginDescriptor* (*dfunc)(void); + const char *errstr; + + if ((module = dlopen (path.c_str(), RTLD_NOW)) == 0) { + error << string_compose(_("PannerManager: cannot load module \"%1\" (%2)"), path, dlerror()) << endmsg; + return 0; + } + + dfunc = (PanPluginDescriptor* (*)(void)) dlsym (module, "panner_descriptor"); + + if ((errstr = dlerror()) != 0) { + error << string_compose(_("PannerManager: module \"%1\" has no descriptor function."), path) << endmsg; + error << errstr << endmsg; + dlclose (module); + return 0; + } + + descriptor = dfunc(); + if (descriptor) { + info = new PannerInfo (*descriptor, module); + } else { + dlclose (module); + } + + return info; +} + +PannerInfo* +PannerManager::select_panner (ChanCount in, ChanCount out) +{ + PanPluginDescriptor* d; + int32_t nin = in.n_audio(); + int32_t nout = out.n_audio(); + + cerr << "Need match for in = " << nin << " out = " << nout << endl; + + /* look for exact match first */ + + for (list::iterator p = panner_info.begin(); p != panner_info.end(); ++p) { + d = &(*p)->descriptor; + + cerr << "\t1. Check panner with in=" << d->in << " out=" << d->out << endl; + + if (d->in == nin && d->out == nout) { + return *p; + } + } + + /* no exact match, look for good fit on inputs and variable on outputs */ + + for (list::iterator p = panner_info.begin(); p != panner_info.end(); ++p) { + d = &(*p)->descriptor; + + cerr << "\t2. Check panner with in=" << d->in << " out=" << d->out << endl; + + if (d->in == nin && d->out == -1) { + return *p; + } + } + + /* no exact match, look for good fit on outputs and variable on inputs */ + + for (list::iterator p = panner_info.begin(); p != panner_info.end(); ++p) { + d = &(*p)->descriptor; + + cerr << "\t3. Check panner with in=" << d->in << " out=" << d->out << endl; + + if (d->in == -1 && d->out == nout) { + return *p; + } + } + + /* no exact match, look for variable fit on inputs and outputs */ + + for (list::iterator p = panner_info.begin(); p != panner_info.end(); ++p) { + d = &(*p)->descriptor; + + cerr << "\t4. Check panner with in=" << d->in << " out=" << d->out << endl; + + if (d->in == -1 && d->out == -1) { + return *p; + } + } + + warning << string_compose (_("no panner discovered for in/out = %1/%2"), nin, nout) << endmsg; + + return 0; +} diff --git a/libs/ardour/panner_search_path.cc b/libs/ardour/panner_search_path.cc new file mode 100644 index 0000000000..559e306caa --- /dev/null +++ b/libs/ardour/panner_search_path.cc @@ -0,0 +1,52 @@ +/* + Copyright (C) 2007 Tim Mayberry + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include + +#include "ardour/panner_search_path.h" +#include "ardour/directory_names.h" +#include "ardour/filesystem_paths.h" + +namespace { + const char * const panner_env_variable_name = "ARDOUR_PANNER_PATH"; +} // anonymous + +using namespace PBD; + +namespace ARDOUR { + +SearchPath +panner_search_path () +{ + bool panner_path_defined = false; + SearchPath spath_env (Glib::getenv(panner_env_variable_name, panner_path_defined)); + + if (panner_path_defined) { + return spath_env; + } + + SearchPath spath(user_config_directory ()); + + spath += ardour_module_directory (); + spath.add_subdirectory_to_paths(panner_dir_name); + + return spath; +} + +} // namespace ARDOUR diff --git a/libs/ardour/panner_shell.cc b/libs/ardour/panner_shell.cc new file mode 100644 index 0000000000..c6cbbe36ab --- /dev/null +++ b/libs/ardour/panner_shell.cc @@ -0,0 +1,311 @@ +/* + Copyright (C) 2004-2011 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "pbd/cartesian.h" +#include "pbd/convert.h" +#include "pbd/error.h" +#include "pbd/failed_constructor.h" +#include "pbd/xml++.h" +#include "pbd/enumwriter.h" + +#include "evoral/Curve.hpp" + +#include "ardour/audio_buffer.h" +#include "ardour/audio_buffer.h" +#include "ardour/automatable.h" +#include "ardour/buffer_set.h" +#include "ardour/pannable.h" +#include "ardour/panner.h" +#include "ardour/panner_manager.h" +#include "ardour/panner_shell.h" +#include "ardour/runtime_functions.h" +#include "ardour/session.h" +#include "ardour/utils.h" + +#include "i18n.h" + +#include "pbd/mathfix.h" + +using namespace std; +using namespace ARDOUR; +using namespace PBD; + +PannerShell::PannerShell (string name, Session& s, boost::shared_ptr p) + : SessionObject (s, name) + , _pannable (p) +{ + set_name (name); +} + +PannerShell::~PannerShell () +{ +} + +void +PannerShell::configure_io (ChanCount in, ChanCount out) +{ + uint32_t nouts = out.n_audio(); + uint32_t nins = in.n_audio(); + + /* if new and old config don't need panning, or if + the config hasn't changed, we're done. + */ + + if (_panner && _panner->in().n_audio() == nins && _panner->out().n_audio() == nouts) { + return; + } + + if (nouts < 2 || nins == 0) { + /* no need for panning with less than 2 outputs or no inputs */ + if (_panner) { + _panner.reset (); + Changed (); /* EMIT SIGNAL */ + } + return; + } + + PannerInfo* pi = PannerManager::instance().select_panner (in, out); + + if (pi == 0) { + abort (); + } + + _panner.reset (pi->descriptor.factory (_pannable, _session.get_speakers())); + + Changed (); /* EMIT SIGNAL */ +} + +XMLNode& +PannerShell::get_state (void) +{ + return state (true); +} + +XMLNode& +PannerShell::state (bool full) +{ + XMLNode* node = new XMLNode ("PannerShell"); + + if (_panner) { + node->add_child_nocopy (_panner->state (full)); + } + + return *node; +} + +int +PannerShell::set_state (const XMLNode& node, int version) +{ + XMLNodeList nlist = node.children (); + XMLNodeConstIterator niter; + const XMLProperty *prop; + LocaleGuard lg (X_("POSIX")); + + _panner.reset (); + + for (niter = nlist.begin(); niter != nlist.end(); ++niter) { + + if ((*niter)->name() == X_("Panner")) { + + if ((prop = (*niter)->property (X_("type")))) { + + list::iterator p; + PannerManager& pm (PannerManager::instance()); + + for (p = pm.panner_info.begin(); p != pm.panner_info.end(); ++p) { + if (prop->value() == (*p)->descriptor.name) { + + /* note that we assume that all the stream panners + are of the same type. pretty good + assumption, but it's still an assumption. + */ + + _panner.reset ((*p)->descriptor.factory (_pannable, _session.get_speakers ())); + + if (_panner->set_state (**niter, version) == 0) { + return -1; + } + + break; + } + } + + if (p == pm.panner_info.end()) { + error << string_compose (_("Unknown panner plugin \"%1\" found in pan state - ignored"), + prop->value()) + << endmsg; + } + + } else { + error << _("panner plugin node has no type information!") + << endmsg; + return -1; + } + } + } + + return 0; +} + + +void +PannerShell::distribute_no_automation (BufferSet& inbufs, BufferSet& outbufs, pframes_t nframes, gain_t gain_coeff) +{ + if (outbufs.count().n_audio() == 0) { + // Don't want to lose audio... + assert(inbufs.count().n_audio() == 0); + return; + } + + if (outbufs.count().n_audio() == 1) { + + /* just one output: no real panning going on */ + + AudioBuffer& dst = outbufs.get_audio(0); + + if (gain_coeff == 0.0f) { + + /* gain was zero, so make it silent */ + + dst.silence (nframes); + + } else if (gain_coeff == 1.0f){ + + /* mix all input buffers into the output */ + + // copy the first + dst.read_from(inbufs.get_audio(0), nframes); + + // accumulate starting with the second + if (inbufs.count().n_audio() > 0) { + BufferSet::audio_iterator i = inbufs.audio_begin(); + for (++i; i != inbufs.audio_end(); ++i) { + dst.merge_from(*i, nframes); + } + } + + } else { + + /* mix all buffers into the output, scaling them all by the gain */ + + // copy the first + dst.read_from(inbufs.get_audio(0), nframes); + + // accumulate (with gain) starting with the second + if (inbufs.count().n_audio() > 0) { + BufferSet::audio_iterator i = inbufs.audio_begin(); + for (++i; i != inbufs.audio_end(); ++i) { + dst.accumulate_with_gain_from(*i, nframes, gain_coeff); + } + } + + } + + return; + } + + /* multiple outputs ... we must have a panner */ + + assert (_panner); + + /* setup silent buffers so that we can mix into the outbuffers (slightly suboptimal - + better to copy the first set of data then mix after that, but hey, its 2011) + */ + + for (BufferSet::audio_iterator b = outbufs.audio_begin(); b != outbufs.audio_end(); ++b) { + (*b).silence (nframes); + } + + _panner->distribute (inbufs, outbufs, gain_coeff, nframes); +} + +void +PannerShell::run (BufferSet& inbufs, BufferSet& outbufs, framepos_t start_frame, framepos_t end_frame, pframes_t nframes) +{ + if (outbufs.count().n_audio() == 0) { + // Failing to deliver audio we were asked to deliver is a bug + assert(inbufs.count().n_audio() == 0); + return; + } + + if (outbufs.count().n_audio() == 1) { + + /* one output only: no panner */ + + AudioBuffer& dst = outbufs.get_audio(0); + + // FIXME: apply gain automation? + + // copy the first + dst.read_from (inbufs.get_audio(0), nframes); + + // accumulate starting with the second + BufferSet::audio_iterator i = inbufs.audio_begin(); + for (++i; i != inbufs.audio_end(); ++i) { + dst.merge_from (*i, nframes); + } + + return; + } + + // More than 1 output + + AutoState as = _panner->automation_state (); + + // If we shouldn't play automation defer to distribute_no_automation + + if (!(as & Play || ((as & Touch) && !_panner->touching()))) { + + // Speed quietning + gain_t gain_coeff = 1.0; + + if (fabsf(_session.transport_speed()) > 1.5f && Config->get_quieten_at_speed ()) { + gain_coeff = speed_quietning; + } + + distribute_no_automation (inbufs, outbufs, nframes, gain_coeff); + + } else { + + /* setup the terrible silence so that we can mix into the outbuffers (slightly suboptimal - + better to copy the first set of data then mix after that, but hey, its 2011) + */ + for (BufferSet::audio_iterator i = outbufs.audio_begin(); i != outbufs.audio_end(); ++i) { + i->silence(nframes); + } + + _panner->distribute_automated (inbufs, outbufs, start_frame, end_frame, nframes, _session.pan_automation_buffer()); + } +} + -- cgit v1.2.3