X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fdelivery.cc;h=79c44ce94a0efa8bd6d9e7b80ea62fe33c9f9212;hb=664e715a002450ac0f079411f6f051c122a4322a;hp=bf152420940a86a30ec2be40ae39edc2778fb0ea;hpb=0569107ddc0d2a8df6ca0a2c8cc16ebe8f3dee99;p=ardour.git diff --git a/libs/ardour/delivery.cc b/libs/ardour/delivery.cc index bf15242094..79c44ce94a 100644 --- a/libs/ardour/delivery.cc +++ b/libs/ardour/delivery.cc @@ -1,16 +1,16 @@ /* - Copyright (C) 2009 Paul Davis - + Copyright (C) 2009 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. @@ -20,141 +20,296 @@ #include #include "pbd/enumwriter.h" -#include "ardour/delivery.h" -#include "ardour/audio_buffer.h" +#include "pbd/convert.h" + +#include "ardour/amp.h" +#include "ardour/audioengine.h" #include "ardour/buffer_set.h" -#include "ardour/configuration.h" +#include "ardour/debug.h" +#include "ardour/delivery.h" #include "ardour/io.h" -#include "ardour/meter.h" +#include "ardour/mute_master.h" +#include "ardour/pannable.h" +#include "ardour/panner_shell.h" +#include "ardour/port.h" #include "ardour/session.h" +#include "i18n.h" + +namespace ARDOUR { class Panner; } + using namespace std; +using namespace PBD; using namespace ARDOUR; +PBD::Signal0 Delivery::PannersLegal; +bool Delivery::panners_legal = false; + /* deliver to an existing IO object */ -Delivery::Delivery (Session& s, IO* io, const string& name, Role r) - : IOProcessor(s, io, name) +Delivery::Delivery (Session& s, boost::shared_ptr io, boost::shared_ptr pannable, + boost::shared_ptr mm, const string& name, Role r) + : IOProcessor(s, boost::shared_ptr(), (role_requires_output_ports (r) ? io : boost::shared_ptr()), name) , _role (r) - , _metering (false) - , _muted_by_self (false) - , _muted_by_others (false) + , _output_buffers (new BufferSet()) + , _current_gain (1.0) + , _no_outs_cuz_we_no_monitor (false) + , _mute_master (mm) + , _no_panner_reset (false) { + if (pannable) { + _panshell = boost::shared_ptr(new PannerShell (_name, _session, pannable)); + } + + _display_to_user = false; + + if (_output) { + _output->changed.connect_same_thread (*this, boost::bind (&Delivery::output_changed, this, _1, _2)); + } } /* deliver to a new IO object */ -Delivery::Delivery (Session& s, const string& name, Role r) - : IOProcessor(s, name) +Delivery::Delivery (Session& s, boost::shared_ptr pannable, boost::shared_ptr mm, const string& name, Role r) + : IOProcessor(s, false, (role_requires_output_ports (r) ? true : false), name, "", DataType::AUDIO, (r == Send)) , _role (r) - , _metering (false) - , _muted_by_self (false) - , _muted_by_others (false) + , _output_buffers (new BufferSet()) + , _current_gain (1.0) + , _no_outs_cuz_we_no_monitor (false) + , _mute_master (mm) + , _no_panner_reset (false) { -} + if (pannable) { + _panshell = boost::shared_ptr(new PannerShell (_name, _session, pannable)); + } -/* reconstruct from XML */ + _display_to_user = false; -Delivery::Delivery (Session& s, const XMLNode& node) - : IOProcessor (s, "reset") - , _role (Role (0)) - , _metering (false) - , _muted_by_self (false) - , _muted_by_others (false) -{ - if (set_state (node)) { - throw failed_constructor (); + if (_output) { + _output->changed.connect_same_thread (*this, boost::bind (&Delivery::output_changed, this, _1, _2)); } } -bool -Delivery::visible () const +Delivery::~Delivery() { - if (_role & (Main|Solo)) { - return false; - } + DEBUG_TRACE (DEBUG::Destruction, string_compose ("delivery %1 destructor\n", _name)); - return true; -} + /* this object should vanish from any signal callback lists + that it is on before we get any further. The full qualification + of the method name is not necessary, but is here to make it + clear that this call is about signals, not data flow connections. + */ -bool -Delivery::can_support_io_configuration (const ChanCount& in, ChanCount& out) const -{ - out = in; - return true; + ScopedConnectionList::drop_connections (); + + delete _output_buffers; } -bool -Delivery::configure_io (ChanCount in, ChanCount out) +std::string +Delivery::display_name () const { - if (out != in) { // always 1:1 - return false; + switch (_role) { + case Main: + return _("main outs"); + break; + case Listen: + return _("listen"); + break; + case Send: + case Insert: + default: + return name(); } - - return Processor::configure_io (in, out); } -void -Delivery::run_in_place (BufferSet& bufs, sframes_t start_frame, sframes_t end_frame, nframes_t nframes) +bool +Delivery::can_support_io_configuration (const ChanCount& in, ChanCount& out) { - if (_io->n_outputs().get (_io->default_type()) == 0) { - return; - } + if (_role == Main) { - if (!active() || _muted_by_self || _muted_by_others) { - silence (nframes); - if (_metering) { - _io->peak_meter().reset(); + /* the out buffers will be set to point to the port output buffers + of our output object. + */ + + if (_output) { + if (_output->n_ports() != ChanCount::ZERO) { + /* increase number of output ports if the processor chain requires it */ + out = ChanCount::max (_output->n_ports(), in); + return true; + } else { + /* not configured yet - we will passthru */ + out = in; + return true; + } + } else { + fatal << "programming error: this should never be reached" << endmsg; + /*NOTREACHED*/ } - } else { - // we have to copy the input, because IO::deliver_output may alter the buffers - // in-place, which a send must never do. - BufferSet& sendbufs = _session.get_mix_buffers (bufs.count()); + } else if (_role == Insert) { - sendbufs.read_from(bufs, nframes); - assert(sendbufs.count() == bufs.count()); + /* the output buffers will be filled with data from the *input* ports + of this Insert. + */ - _io->deliver_output (sendbufs, start_frame, end_frame, nframes); - - if (_metering) { - if (_io->effective_gain() == 0) { - _io->peak_meter().reset(); + if (_input) { + if (_input->n_ports() != ChanCount::ZERO) { + out = _input->n_ports(); + return true; } else { - _io->peak_meter().run_in_place(_io->output_buffers(), start_frame, end_frame, nframes); + /* not configured yet - we will passthru */ + out = in; + return true; } + } else { + fatal << "programming error: this should never be reached" << endmsg; + /*NOTREACHED*/ } + + } else { + fatal << "programming error: this should never be reached" << endmsg; } + + return false; } -void -Delivery::set_metering (bool yn) +/** Caller must hold process lock */ +bool +Delivery::configure_io (ChanCount in, ChanCount out) { - _metering = yn; - - if (!_metering) { - /* XXX possible thread hazard here */ - _io->peak_meter().reset(); +#ifndef NDEBUG + bool r = AudioEngine::instance()->process_lock().trylock(); + assert (!r && "trylock inside Delivery::configure_io"); +#endif + + /* check configuration by comparison with our I/O port configuration, if appropriate. + see ::can_support_io_configuration() for comments + */ + + if (_role == Main) { + + if (_output) { + if (_output->n_ports() != out) { + if (_output->n_ports() != ChanCount::ZERO) { + _output->ensure_io (out, false, this); + } else { + /* I/O not yet configured */ + } + } + } + + } else if (_role == Insert) { + + if (_input) { + if (_input->n_ports() != in) { + if (_input->n_ports() != ChanCount::ZERO) { + fatal << _name << " programming error: configure_io called with " << in << " and " << out << " with " << _input->n_ports() << " input ports" << endmsg; + /*NOTREACHED*/ + } else { + /* I/O not yet configured */ + } + } + } + } -} -void -Delivery::set_self_mute (bool yn) -{ - if (yn != _muted_by_self) { - _muted_by_self = yn; - SelfMuteChange (); // emit signal + + if (!Processor::configure_io (in, out)) { + return false; } + + reset_panner (); + + return true; } void -Delivery::set_nonself_mute (bool yn) +Delivery::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, pframes_t nframes, bool result_required) { - if (yn != _muted_by_others) { - _muted_by_others = yn; - OtherMuteChange (); // emit signal + assert (_output); + + PortSet& ports (_output->ports()); + gain_t tgain; + + if (_output->n_ports ().get (_output->default_type()) == 0) { + goto out; + } + + if (!_active && !_pending_active) { + _output->silence (nframes); + goto out; + } + + /* this setup is not just for our purposes, but for anything that comes after us in the + processing pathway that wants to use this->output_buffers() for some reason. + */ + + output_buffers().get_backend_port_addresses (ports, nframes); + + // this Delivery processor is not a derived type, and thus we assume + // we really can modify the buffers passed in (it is almost certainly + // the main output stage of a Route). Contrast with Send::run() + // which cannot do this. + + tgain = target_gain (); + + if (tgain != _current_gain) { + /* target gain has changed */ + + Amp::apply_gain (bufs, nframes, _current_gain, tgain); + _current_gain = tgain; + + } else if (tgain == 0.0) { + + /* we were quiet last time, and we're still supposed to be quiet. + Silence the outputs, and make sure the buffers are quiet too, + */ + + _output->silence (nframes); + if (result_required) { + bufs.set_count (output_buffers().count ()); + Amp::apply_simple_gain (bufs, nframes, 0.0); + } + goto out; + + } else if (tgain != 1.0) { + + /* target gain has not changed, but is not unity */ + Amp::apply_simple_gain (bufs, nframes, tgain); + } + + if (_panshell && !_panshell->bypassed() && _panshell->panner()) { + + // Use the panner to distribute audio to output port buffers + + _panshell->run (bufs, output_buffers(), start_frame, end_frame, nframes); + + // MIDI data will not have been delivered by the panner + + if (bufs.count().n_midi() > 0 && ports.count().n_midi () > 0) { + _output->copy_to_outputs (bufs, DataType::MIDI, nframes, 0); + } + + } else { + + // Do a 1:1 copy of data to output ports + + if (bufs.count().n_audio() > 0 && ports.count().n_audio () > 0) { + _output->copy_to_outputs (bufs, DataType::AUDIO, nframes, 0); + } + + if (bufs.count().n_midi() > 0 && ports.count().n_midi () > 0) { + _output->copy_to_outputs (bufs, DataType::MIDI, nframes, 0); + } } + + if (result_required) { + bufs.read_from (output_buffers (), nframes); + } + + out: + _active = _pending_active; } XMLNode& @@ -170,34 +325,258 @@ Delivery::state (bool full_state) node.add_property("type", "delivery"); } - node.add_property("metering", (_metering ? "yes" : "no")); - node.add_property("self-muted", (_muted_by_self ? "yes" : "no")); - node.add_property("other-muted", (_muted_by_others ? "yes" : "no")); node.add_property("role", enum_2_string(_role)); + if (_panshell) { + node.add_child_nocopy (_panshell->get_state ()); + } + return node; } int -Delivery::set_state (const XMLNode& node) +Delivery::set_state (const XMLNode& node, int version) { const XMLProperty* prop; - + + if (IOProcessor::set_state (node, version)) { + return -1; + } + if ((prop = node.property ("role")) != 0) { _role = Role (string_2_enum (prop->value(), _role)); + // std::cerr << this << ' ' << _name << " set role to " << enum_2_string (_role) << std::endl; + } else { + // std::cerr << this << ' ' << _name << " NO ROLE INFO\n"; } - if ((prop = node.property ("metering")) != 0) { - set_metering (prop->value() == "yes"); + XMLNode* pan_node = node.child (X_("PannerShell")); + + if (pan_node && _panshell) { + _panshell->set_state (*pan_node, version); } - if ((prop = node.property ("self-muted")) != 0) { - set_self_mute (prop->value() == "yes"); + reset_panner (); + + return 0; +} + +void +Delivery::unpan () +{ + /* caller must hold process lock */ + + _panshell.reset (); +} + +uint32_t +Delivery::pan_outs () const +{ + if (_output) { + return _output->n_ports().n_audio(); + } + + return _configured_output.n_audio(); +} + +void +Delivery::reset_panner () +{ + if (panners_legal) { + if (!_no_panner_reset) { + + if (_panshell) { + _panshell->configure_io (ChanCount (DataType::AUDIO, pans_required()), ChanCount (DataType::AUDIO, pan_outs())); + + if (_role == Main) { + _panshell->pannable()->set_panner (_panshell->panner()); + } + } + } + + } else { + panner_legal_c.disconnect (); + PannersLegal.connect_same_thread (panner_legal_c, boost::bind (&Delivery::panners_became_legal, this)); } +} - if ((prop = node.property ("other-muted")) != 0) { - set_nonself_mute (prop->value() == "yes"); +void +Delivery::panners_became_legal () +{ + if (_panshell) { + _panshell->configure_io (ChanCount (DataType::AUDIO, pans_required()), ChanCount (DataType::AUDIO, pan_outs())); + + if (_role == Main) { + _panshell->pannable()->set_panner (_panshell->panner()); + } } + panner_legal_c.disconnect (); +} + +void +Delivery::defer_pan_reset () +{ + _no_panner_reset = true; +} + +void +Delivery::allow_pan_reset () +{ + _no_panner_reset = false; + reset_panner (); +} + + +int +Delivery::disable_panners () +{ + panners_legal = false; return 0; } + +void +Delivery::reset_panners () +{ + panners_legal = true; + PannersLegal (); +} + +void +Delivery::flush_buffers (framecnt_t nframes) +{ + /* io_lock, not taken: function must be called from Session::process() calltree */ + + if (!_output) { + return; + } + + PortSet& ports (_output->ports()); + + for (PortSet::iterator i = ports.begin(); i != ports.end(); ++i) { + i->flush_buffers (nframes); + } +} + +void +Delivery::transport_stopped (framepos_t now) +{ + Processor::transport_stopped (now); + + if (_panshell) { + _panshell->pannable()->transport_stopped (now); + } + + if (_output) { + PortSet& ports (_output->ports()); + + for (PortSet::iterator i = ports.begin(); i != ports.end(); ++i) { + i->transport_stopped (); + } + } +} + +void +Delivery::realtime_locate () +{ + if (_output) { + PortSet& ports (_output->ports()); + + for (PortSet::iterator i = ports.begin(); i != ports.end(); ++i) { + i->realtime_locate (); + } + } +} + +gain_t +Delivery::target_gain () +{ + /* if we've been requested to deactivate, our target gain is zero */ + + if (!_pending_active) { + return 0.0; + } + + /* if we've been told not to output because its a monitoring situation and + we're not monitoring, then be quiet. + */ + + if (_no_outs_cuz_we_no_monitor) { + return 0.0; + } + + MuteMaster::MutePoint mp = MuteMaster::Main; // stupid gcc uninit warning + + switch (_role) { + case Main: + mp = MuteMaster::Main; + break; + case Listen: + mp = MuteMaster::Listen; + break; + case Send: + case Insert: + case Aux: + if (_pre_fader) { + mp = MuteMaster::PreFader; + } else { + mp = MuteMaster::PostFader; + } + break; + } + + gain_t desired_gain = _mute_master->mute_gain_at (mp); + + if (_role == Listen && _session.monitor_out() && !_session.listening()) { + + /* nobody is soloed, and this delivery is a listen-send to the + control/monitor/listen bus, we should be silent since + it gets its signal from the master out. + */ + + desired_gain = 0.0; + + } + + return desired_gain; +} + +void +Delivery::no_outs_cuz_we_no_monitor (bool yn) +{ + _no_outs_cuz_we_no_monitor = yn; +} + +bool +Delivery::set_name (const std::string& name) +{ + bool ret = IOProcessor::set_name (name); + + if (ret) { + ret = _panshell->set_name (name); + } + + return ret; +} + +bool ignore_output_change = false; + +void +Delivery::output_changed (IOChange change, void* /*src*/) +{ + if (change.type & IOChange::ConfigurationChanged) { + reset_panner (); + _output_buffers->attach_buffers (_output->ports ()); + } +} + +boost::shared_ptr +Delivery::panner () const +{ + if (_panshell) { + return _panshell->panner(); + } else { + return boost::shared_ptr(); + } +} +