X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fpanner.cc;h=cb2ad8dd4a127aa04ed76869f311380eaa9d52c4;hb=0bee288b5d6c6ca1fb33b8bc3b584c6d2c3d3364;hp=e438742266100a9c6f5591f518c5b3cb26cc4ab2;hpb=553cf2982c4905c5a08f305ce2772beaa8c50324;p=ardour.git diff --git a/libs/ardour/panner.cc b/libs/ardour/panner.cc index e438742266..cb2ad8dd4a 100644 --- a/libs/ardour/panner.cc +++ b/libs/ardour/panner.cc @@ -1,3 +1,4 @@ + /* Copyright (C) 2004 Paul Davis @@ -33,6 +34,7 @@ #include #include "pbd/cartesian.h" +#include "pbd/convert.h" #include "pbd/error.h" #include "pbd/failed_constructor.h" #include "pbd/xml++.h" @@ -73,14 +75,14 @@ static double direct_control_to_stereo_pan (double fract) StreamPanner::StreamPanner (Panner& p, Evoral::Parameter param) : parent (p) + , _control (new PanControllable (parent.session(), _("direction"), this, param)) { assert (param.type() != NullAutomation); _muted = false; _mono = false; - /* get our AutomationControl from our parent Panner, creating it if required */ - _control = boost::dynamic_pointer_cast (parent.control (param, true)); + p.add_control (_control); } StreamPanner::~StreamPanner () @@ -96,15 +98,48 @@ StreamPanner::set_mono (bool yn) } } +double +StreamPanner::PanControllable::lower () const +{ + switch (parameter().id()) { + case 200: /* width */ + return -1.0; + default: + return 0.0; + } +} + void -Panner::PanControllable::set_value (double val) +StreamPanner::PanControllable::set_value (double val) { - panner.streampanner (parameter().id()).set_position (AngularVector (direct_control_to_stereo_pan (val), 0.0)); - AutomationControl::set_value(val); + Panner& p (streampanner->get_parent()); + switch (parameter().id()) { + case 100: + /* position */ + val = max (min (val, 1.0), 0.0); + if (p.set_stereo_pan (val, p.width_control()->get_value())) { + AutomationControl::set_value(val); + } + break; + + case 200: + /* width */ + val = max (min (val, 1.0), -1.0); + if (p.set_stereo_pan (p.direction_control()->get_value(), val)) { + AutomationControl::set_value(val); + } + break; + + default: + streampanner->set_position (AngularVector (direct_control_to_stereo_pan (val), 0.0)); + AutomationControl::set_value(val); + break; + } + } double -Panner::PanControllable::get_value (void) const +StreamPanner::PanControllable::get_value (void) const { return AutomationControl::get_value(); } @@ -125,8 +160,8 @@ StreamPanner::set_position (const AngularVector& av, bool link_call) parent.set_position (av, *this); } - if (_angles != av) { - _angles = av; + if (_angles != av) { + _angles = av; update (); Changed (); _control->Changed (); @@ -134,7 +169,7 @@ StreamPanner::set_position (const AngularVector& av, bool link_call) } int -StreamPanner::set_state (const XMLNode& node, int /*version*/) +StreamPanner::set_state (const XMLNode& node, int version) { const XMLProperty* prop; XMLNodeConstIterator iter; @@ -147,18 +182,29 @@ StreamPanner::set_state (const XMLNode& node, int /*version*/) set_mono (string_is_affirmative (prop->value())); } + for (XMLNodeConstIterator iter = node.children().begin(); iter != node.children().end(); ++iter) { + if ((*iter)->name() == Controllable::xml_node_name) { + if ((prop = (*iter)->property ("name")) != 0 && prop->value() == "direction") { + _control->set_state (**iter, version); + } + } + } + return 0; } -void -StreamPanner::add_state (XMLNode& node) +XMLNode& +StreamPanner::get_state () { - node.add_property (X_("muted"), (muted() ? "yes" : "no")); - node.add_property (X_("mono"), (_mono ? "yes" : "no")); + XMLNode* node = new XMLNode (X_("StreamPanner")); + node->add_property (X_("muted"), (muted() ? "yes" : "no")); + node->add_property (X_("mono"), (_mono ? "yes" : "no")); + node->add_child_nocopy (_control->get_state ()); + return *node; } void -StreamPanner::distribute (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, nframes_t nframes) +StreamPanner::distribute (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes) { if (_mono) { /* we're in mono mode, so just pan the input to all outputs equally */ @@ -174,7 +220,7 @@ StreamPanner::distribute (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, void StreamPanner::distribute_automated (AudioBuffer& src, BufferSet& obufs, - nframes_t start, nframes_t end, nframes_t nframes, pan_t** buffers) + framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers) { if (_mono) { /* we're in mono mode, so just pan the input to all outputs equally */ @@ -214,7 +260,7 @@ BaseStereoPanner::load (istream& in, string path, uint32_t& linecnt) _control->list()->clear (); while (in.getline (line, sizeof (line), '\n')) { - nframes_t when; + framepos_t when; double value; ++linecnt; @@ -223,7 +269,7 @@ BaseStereoPanner::load (istream& in, string path, uint32_t& linecnt) break; } - if (sscanf (line, "%" PRIu32 " %lf", &when, &value) != 2) { + if (sscanf (line, "%" PRIu64 " %lf", &when, &value) != 2) { warning << string_compose(_("badly formatted pan automation event record at line %1 of %2 (ignored) [%3]"), linecnt, path, line) << endmsg; continue; } @@ -239,7 +285,7 @@ BaseStereoPanner::load (istream& in, string path, uint32_t& linecnt) } void -BaseStereoPanner::do_distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, nframes_t nframes) +BaseStereoPanner::do_distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes) { assert(obufs.count().n_audio() == 2); @@ -262,8 +308,8 @@ BaseStereoPanner::do_distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t g /* we've moving the pan by an appreciable amount, so we must interpolate over 64 frames or nframes, whichever is smaller */ - nframes_t const limit = min ((nframes_t)64, nframes); - nframes_t n; + pframes_t const limit = min ((pframes_t) 64, nframes); + pframes_t n; delta = -(delta / (float) (limit)); @@ -319,8 +365,8 @@ BaseStereoPanner::do_distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t g /* we're moving the pan by an appreciable amount, so we must interpolate over 64 frames or nframes, whichever is smaller */ - nframes_t const limit = min ((nframes_t)64, nframes); - nframes_t n; + pframes_t const limit = min ((pframes_t) 64, nframes); + pframes_t n; delta = -(delta / (float) (limit)); @@ -397,7 +443,7 @@ EqualPowerStereoPanner::update () x == 1 => hard right = 0.0 degrees */ - double _x = BaseStereoPanner::azimuth_to_lr_fract (_angles.azi); + double _x = BaseStereoPanner::azimuth_to_lr_fract (_angles.azi); float const panR = _x; float const panL = 1 - panR; @@ -414,7 +460,7 @@ EqualPowerStereoPanner::update () void EqualPowerStereoPanner::do_distribute_automated (AudioBuffer& srcbuf, BufferSet& obufs, - nframes_t start, nframes_t end, nframes_t nframes, + framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers) { assert (obufs.count().n_audio() == 2); @@ -450,7 +496,7 @@ EqualPowerStereoPanner::do_distribute_automated (AudioBuffer& srcbuf, BufferSet& const float pan_law_attenuation = -3.0f; const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f); - for (nframes_t n = 0; n < nframes; ++n) { + for (pframes_t n = 0; n < nframes; ++n) { float const panR = buffers[0][n]; float const panL = 1 - panR; @@ -464,7 +510,7 @@ EqualPowerStereoPanner::do_distribute_automated (AudioBuffer& srcbuf, BufferSet& dst = obufs.get_audio(0).data(); pbuf = buffers[0]; - for (nframes_t n = 0; n < nframes; ++n) { + for (pframes_t n = 0; n < nframes; ++n) { dst[n] += src[n] * pbuf[n]; } @@ -475,7 +521,7 @@ EqualPowerStereoPanner::do_distribute_automated (AudioBuffer& srcbuf, BufferSet& dst = obufs.get_audio(1).data(); pbuf = buffers[1]; - for (nframes_t n = 0; n < nframes; ++n) { + for (pframes_t n = 0; n < nframes; ++n) { dst[n] += src[n] * pbuf[n]; } @@ -497,54 +543,26 @@ EqualPowerStereoPanner::get_state (void) XMLNode& EqualPowerStereoPanner::state (bool /*full_state*/) { - XMLNode* root = new XMLNode ("StreamPanner"); - char buf[64]; - LocaleGuard lg (X_("POSIX")); - - snprintf (buf, sizeof (buf), "%.12g", _angles.azi); - root->add_property (X_("azimuth"), buf); - root->add_property (X_("type"), EqualPowerStereoPanner::name); - - // XXX: dont save automation here... its part of the automatable panner now. - - StreamPanner::add_state (*root); - - root->add_child_nocopy (_control->get_state ()); - - return *root; + XMLNode& root (StreamPanner::get_state ()); + root.add_property (X_("type"), EqualPowerStereoPanner::name); + return root; } int EqualPowerStereoPanner::set_state (const XMLNode& node, int version) { - const XMLProperty* prop; LocaleGuard lg (X_("POSIX")); - if ((prop = node.property (X_("azimuth")))) { - AngularVector a (atof (prop->value().c_str()), 0.0); - set_position (a, true); - } else if ((prop = node.property (X_("x")))) { - /* old school cartesian positioning */ - AngularVector a; - a.azi = BaseStereoPanner::lr_fract_to_azimuth (atof (prop->value().c_str())); - set_position (a, true); - } - StreamPanner::set_state (node, version); for (XMLNodeConstIterator iter = node.children().begin(); iter != node.children().end(); ++iter) { - if ((*iter)->name() == X_("Controllable")) { - if ((prop = (*iter)->property("name")) != 0 && prop->value() == "panner") { - _control->set_state (**iter, version); - } - - } else if ((*iter)->name() == X_("Automation")) { - + if ((*iter)->name() == X_("Automation")) { + _control->alist()->set_state (*((*iter)->children().front()), version); - + if (_control->alist()->automation_state() != Off) { - double degrees = BaseStereoPanner::lr_fract_to_azimuth (_control->list()->eval (parent.session().transport_frame())); + double degrees = BaseStereoPanner::lr_fract_to_azimuth (_control->list()->eval (parent.session().transport_frame())); set_position (AngularVector (degrees, 0.0)); } } @@ -555,7 +573,7 @@ EqualPowerStereoPanner::set_state (const XMLNode& node, int version) Panner::Panner (string name, Session& s) : SessionObject (s, name) - , Automatable (s) + , Automatable (s) { //set_name_old_auto (name); set_name (name); @@ -613,19 +631,19 @@ Panner::reset_to_default () } if (outputs.size() == 2) { - AngularVector a; + AngularVector a; switch (_streampanners.size()) { case 1: - a.azi = 90.0; /* "front" or "top", in degrees */ + a.azi = 90.0; /* "front" or "top", in degrees */ _streampanners.front()->set_position (a); _streampanners.front()->pan_control()->list()->reset_default (0.5); return; break; case 2: - a.azi = 180.0; /* "left", in degrees */ + a.azi = 180.0; /* "left", in degrees */ _streampanners.front()->set_position (a); _streampanners.front()->pan_control()->list()->reset_default (0.0); - a.azi = 0.0; /* "right", in degrees */ + a.azi = 0.0; /* "right", in degrees */ _streampanners.back()->set_position (a); _streampanners.back()->pan_control()->list()->reset_default (1.0); return; @@ -645,7 +663,7 @@ Panner::reset_to_default () void Panner::reset_streampanner (uint32_t which) { - AngularVector a; + AngularVector a; if (which >= _streampanners.size() || which >= outputs.size()) { return; @@ -660,20 +678,20 @@ Panner::reset_streampanner (uint32_t which) switch (_streampanners.size()) { case 1: /* stereo out, 1 stream, default = middle */ - a.azi = 90.0; /* "front" or "top", in degrees */ + a.azi = 90.0; /* "front" or "top", in degrees */ _streampanners.front()->set_position (a); _streampanners.front()->pan_control()->list()->reset_default (0.5); break; case 2: /* stereo out, 2 streams, default = hard left/right */ if (which == 0) { - a.azi = 180.0; /* "left", in degrees */ - _streampanners.front()->set_position (a); - _streampanners.front()->pan_control()->list()->reset_default (0.0); - } else { - a.azi = 0.0; /* "right", in degrees */ - _streampanners.back()->set_position (a); - _streampanners.back()->pan_control()->list()->reset_default (1.0); + a.azi = 180.0; /* "left", in degrees */ + _streampanners.front()->set_position (a); + _streampanners.front()->pan_control()->list()->reset_default (0.0); + } else { + a.azi = 0.0; /* "right", in degrees */ + _streampanners.back()->set_position (a); + _streampanners.back()->pan_control()->list()->reset_default (1.0); } break; } @@ -747,10 +765,10 @@ Panner::reset (uint32_t nouts, uint32_t npans) for (n = 0; n < npans; ++n) { _streampanners.push_back (new EqualPowerStereoPanner (*this, Evoral::Parameter(PanAutomation, 0, n))); } - break; + break; - default: - setup_speakers (nouts); + default: + setup_speakers (nouts); for (n = 0; n < npans; ++n) { _streampanners.push_back (new VBAPanner (*this, Evoral::Parameter(PanAutomation, 0, n), _session.get_speakers())); } @@ -761,6 +779,8 @@ Panner::reset (uint32_t nouts, uint32_t npans) (*x)->update (); } + setup_meta_controls (); + /* must emit Changed here, otherwise the changes to the pan_control below raise further signals which the GUI is not prepared for until it has seen the Changed here. */ @@ -784,7 +804,7 @@ Panner::reset (uint32_t nouts, uint32_t npans) left = _streampanners.front()->get_position (); right = _streampanners.back()->get_position (); - if (changed || ((left.azi == 0.0) && (right.azi == 0.0))) { + if (changed || ((left.azi == 0.0) && (right.azi == 0.0))) { _streampanners.front()->set_position (AngularVector (180.0, 0.0)); _streampanners.front()->pan_control()->list()->reset_default (0.0); @@ -795,26 +815,26 @@ Panner::reset (uint32_t nouts, uint32_t npans) } else if (npans > 1 && outputs.size() > 2) { - /* 2d panning: spread signals equally around a circle */ + /* 2d panning: spread signals equally around a circle */ - double degree_step = 360.0 / nouts; - double deg; + double degree_step = 360.0 / nouts; + double deg; - /* even number of signals? make sure the top two are either side of "top". - otherwise, just start at the "top" (90.0 degrees) and rotate around - */ + /* even number of signals? make sure the top two are either side of "top". + otherwise, just start at the "top" (90.0 degrees) and rotate around + */ - if (npans % 2) { - deg = 90.0 - degree_step; - } else { - deg = 90.0; - } + if (npans % 2) { + deg = 90.0 - degree_step; + } else { + deg = 90.0; + } - for (std::vector::iterator x = _streampanners.begin(); x != _streampanners.end(); ++x) { - (*x)->set_position (AngularVector (deg, 0.0)); - deg += degree_step; - } - } + for (std::vector::iterator x = _streampanners.begin(); x != _streampanners.end(); ++x) { + (*x)->set_position (AngularVector (deg, 0.0)); + deg += degree_step; + } + } } void @@ -915,21 +935,14 @@ Panner::state (bool full) node->add_property (X_("linked"), (_linked ? "yes" : "no")); node->add_property (X_("link_direction"), enum_2_string (_link_direction)); node->add_property (X_("bypassed"), (bypassed() ? "yes" : "no")); - - for (vector::iterator o = outputs.begin(); o != outputs.end(); ++o) { - XMLNode* onode = new XMLNode (X_("Output")); - snprintf (buf, sizeof (buf), "%.12g", (*o).position.azi); - onode->add_property (X_("azimuth"), buf); - snprintf (buf, sizeof (buf), "%.12g", (*o).position.ele); - onode->add_property (X_("elevation"), buf); - node->add_child_nocopy (*onode); - } + snprintf (buf, sizeof (buf), "%zd", outputs.size()); + node->add_property (X_("outputs"), buf); for (vector::const_iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { node->add_child_nocopy ((*i)->state (full)); } - node->add_child_nocopy (get_automation_xml_state ()); + node->add_child_nocopy (get_automation_xml_state ()); return *node; } @@ -937,7 +950,7 @@ Panner::state (bool full) int Panner::set_state (const XMLNode& node, int version) { - XMLNodeList nlist; + XMLNodeList nlist = node.children (); XMLNodeConstIterator niter; const XMLProperty *prop; uint32_t i; @@ -966,27 +979,38 @@ Panner::set_state (const XMLNode& node, int version) set_link_direction (LinkDirection (string_2_enum (prop->value(), ld))); } - nlist = node.children(); + if ((prop = node.property (X_("outputs"))) != 0) { + uint32_t n = atoi (prop->value()); - for (niter = nlist.begin(); niter != nlist.end(); ++niter) { - if ((*niter)->name() == X_("Output")) { - - AngularVector a; - - if ((prop = (*niter)->property (X_("azimuth")))) { - sscanf (prop->value().c_str(), "%lg", &a.azi); - } else if ((prop = (*niter)->property (X_("x")))) { - /* old school cartesian */ - a.azi = BaseStereoPanner::lr_fract_to_azimuth (atof (prop->value().c_str())); - } + while (n--) { + AngularVector a; // value is irrelevant + outputs.push_back (Output (a)); + } - if ((prop = (*niter)->property (X_("elevation")))) { - sscanf (prop->value().c_str(), "%lg", &a.ele); + } else { + + /* old school */ + + for (niter = nlist.begin(); niter != nlist.end(); ++niter) { + if ((*niter)->name() == X_("Output")) { + + AngularVector a; + + if ((prop = (*niter)->property (X_("azimuth")))) { + sscanf (prop->value().c_str(), "%lg", &a.azi); + } else if ((prop = (*niter)->property (X_("x")))) { + /* old school cartesian */ + a.azi = BaseStereoPanner::lr_fract_to_azimuth (atof (prop->value().c_str())); + } + + if ((prop = (*niter)->property (X_("elevation")))) { + sscanf (prop->value().c_str(), "%lg", &a.ele); + } + + outputs.push_back (Output (a)); } - - outputs.push_back (Output (a)); - } - } + } + } for (niter = nlist.begin(); niter != nlist.end(); ++niter) { @@ -1008,13 +1032,12 @@ Panner::set_state (const XMLNode& node, int version) if (sp->set_state (**niter, version) == 0) { _streampanners.push_back (sp); - } + } break; } } - if (!pan_plugins[i].factory) { error << string_compose (_("Unknown panner plugin \"%1\" found in pan state - ignored"), prop->value()) @@ -1030,7 +1053,10 @@ Panner::set_state (const XMLNode& node, int version) } } + setup_meta_controls (); + reset (outputs.size (), num_panners); + /* don't try to do old-school automation loading if it wasn't marked as existing */ if ((prop = node.property (X_("automation")))) { @@ -1045,7 +1071,7 @@ Panner::set_state (const XMLNode& node, int version) set_automation_xml_state (**niter, Evoral::Parameter (PanAutomation)); } } - + return 0; } @@ -1064,10 +1090,10 @@ Panner::touching () const void Panner::set_position (const AngularVector& a, StreamPanner& orig) { - AngularVector delta; - AngularVector new_position; + AngularVector delta; + AngularVector new_position; - delta = orig.get_position() - a; + delta = orig.get_position() - a; if (_link_direction == SameDirection) { @@ -1075,7 +1101,7 @@ Panner::set_position (const AngularVector& a, StreamPanner& orig) if (*i == &orig) { (*i)->set_position (a, true); } else { - new_position = (*i)->get_position() + delta; + new_position = (*i)->get_position() + delta; (*i)->set_position (new_position, true); } } @@ -1086,7 +1112,7 @@ Panner::set_position (const AngularVector& a, StreamPanner& orig) if (*i == &orig) { (*i)->set_position (a, true); } else { - new_position = (*i)->get_position() - delta; + new_position = (*i)->get_position() - delta; (*i)->set_position (new_position, true); } } @@ -1094,7 +1120,7 @@ Panner::set_position (const AngularVector& a, StreamPanner& orig) } void -Panner::distribute_no_automation (BufferSet& inbufs, BufferSet& outbufs, nframes_t nframes, gain_t gain_coeff) +Panner::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... @@ -1165,7 +1191,7 @@ Panner::distribute_no_automation (BufferSet& inbufs, BufferSet& outbufs, nframes } void -Panner::run (BufferSet& inbufs, BufferSet& outbufs, framepos_t start_frame, framepos_t end_frame, nframes_t nframes) +Panner::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 @@ -1343,47 +1369,184 @@ Panner::value_as_string (double v) void Panner::setup_speakers (uint32_t nouts) { - switch (nouts) { - case 3: - /* top, bottom kind-of-left & bottom kind-of-right */ - outputs.push_back (AngularVector (90.0, 0.0)); - outputs.push_back (AngularVector (215.0, 0,0)); - outputs.push_back (AngularVector (335.0, 0,0)); - break; - case 4: - /* clockwise from top left */ - outputs.push_back (AngularVector (135.0, 0.0)); - outputs.push_back (AngularVector (45.0, 0.0)); - outputs.push_back (AngularVector (335.0, 0.0)); - outputs.push_back (AngularVector (215.0, 0.0)); - break; + switch (nouts) { + case 3: + /* top, bottom kind-of-left & bottom kind-of-right */ + outputs.push_back (AngularVector (90.0, 0.0)); + outputs.push_back (AngularVector (215.0, 0,0)); + outputs.push_back (AngularVector (335.0, 0,0)); + break; + case 4: + /* clockwise from top left */ + outputs.push_back (AngularVector (135.0, 0.0)); + outputs.push_back (AngularVector (45.0, 0.0)); + outputs.push_back (AngularVector (335.0, 0.0)); + outputs.push_back (AngularVector (215.0, 0.0)); + break; default: - { - double degree_step = 360.0 / nouts; - double deg; - uint32_t n; - - /* even number of speakers? make sure the top two are either side of "top". - otherwise, just start at the "top" (90.0 degrees) and rotate around - */ - - if (nouts % 2) { - deg = 90.0 - degree_step; - } else { - deg = 90.0; - } + { + double degree_step = 360.0 / nouts; + double deg; + uint32_t n; + + /* even number of speakers? make sure the top two are either side of "top". + otherwise, just start at the "top" (90.0 degrees) and rotate around + */ + + if (nouts % 2) { + deg = 90.0 - degree_step; + } else { + deg = 90.0; + } for (n = 0; n < nouts; ++n, deg += degree_step) { outputs.push_back (Output (AngularVector (deg, 0.0))); } + } + } + + Speakers& speakers (_session.get_speakers()); + + speakers.clear_speakers (); + + for (vector::iterator o = outputs.begin(); o != outputs.end(); ++o) { + speakers.add_speaker ((*o).position); + } +} + +void +Panner::set_stereo_width (double val) +{ + boost::shared_ptr dc = direction_control(); + if (dc) { + dc->set_value (val); + } +} + +void +Panner::set_stereo_position (double val) +{ + boost::shared_ptr wc = width_control(); + if (wc) { + wc->set_value (val); } +} + +bool +Panner::set_stereo_pan (double direction_as_lr_fract, double width) +{ + AngularVector p (BaseStereoPanner::lr_fract_to_azimuth (direction_as_lr_fract), 0.0); + /* width parameter ranges from -1..+1 with 0.0 at center. we want 0..+1 plus knowing + whether its "reversed" (i.e. left signal pans right and right signal pans left). + full width = 180 degrees + */ + double spread = 2.0 * fabs(width/2.0) * 180.0; + double l_pos = p.azi + (spread/2.0); /* more left is "increasing degrees" */ + double r_pos = p.azi - (spread/2.0); /* more right is "decreasing degrees" */ + bool move_left = true; + bool move_right = true; + int l_index = 0; + int r_index = 1; + + assert (_streampanners.size() > 1); + + if (width < 0.0) { + swap (l_index, r_index); } - Speakers& speakers (_session.get_speakers()); - - speakers.clear_speakers (); + l_pos = max (min (l_pos, 180.0), 0.0); + r_pos = max (min (r_pos, 180.0), 0.0); + + /* if the new left position is less than or equal to 180 (hard left) and the left panner + is already there, we're not moving the left signal. + */ + + if (l_pos >= 180.0 &&_streampanners[l_index]->get_position().azi == 180.0) { + move_left = false; + } + + /* if the new right position is less than or equal to zero (hard right) and the right panner + is already there, we're not moving the right signal. + */ + + if (r_pos <= 0.0 && _streampanners[r_index]->get_position().azi == 0.0) { + move_right = false; + } + + if (move_left && move_right) { + _streampanners[l_index]->set_position (AngularVector (l_pos, 0.0)); + _streampanners[r_index]->set_position (AngularVector (r_pos, 0.0)); + } + + return move_left && move_right; +} + +void +Panner::setup_meta_controls () +{ + if (_streampanners.size() != 2 || outputs.size() != 2) { + return; + } + + /* 2 signals to 2 outputs: provide "classic" controls for easier manipulation. + + The ID numbers used here don't really matter that much, because Parameters are scoped by owner, + but they keep us out of the ordinary range of pan-related parameters. + */ + + Evoral::Parameter lr_param (PanAutomation, 0, 100); + Evoral::Parameter width_param (PanAutomation, 0, 200); + boost::shared_ptr dc = automation_control (lr_param); + boost::shared_ptr wc = automation_control (width_param); + + if (dc) { + /* reset parent StreamPanner as the current one may have been deleted */ + boost::shared_ptr p = boost::dynamic_pointer_cast (dc); + assert (p); + p->streampanner = _streampanners.front (); + } else { + dc.reset (new StreamPanner::PanControllable (_session, _("lr"), _streampanners.front(), lr_param)); + add_control (dc); + } + + if (wc) { + /* reset parent as above */ + boost::shared_ptr p = boost::dynamic_pointer_cast (wc); + assert (p); + p->streampanner = _streampanners.front (); + } else { + wc.reset (new StreamPanner::PanControllable (_session, _("width"), _streampanners.front(), width_param)); + add_control (wc); + } - for (vector::iterator o = outputs.begin(); o != outputs.end(); ++o) { - speakers.add_speaker ((*o).position); + dc->set_value (0.5); + wc->set_value (1.0); // full width +} + +string +Panner::describe_parameter (Evoral::Parameter param) +{ + if (param.type() == PanAutomation) { + switch (param.id()) { + case 100: + return "Pan:position"; + case 200: + return "Pan:width"; + default: + if (_streampanners.size() == 2) { + switch (param.id()) { + case 0: + return "Pan L"; + default: + return "Pan R"; + } + } else { + stringstream ss; + ss << "Pan " << param.id() + 1; + return ss.str (); + } + } } + + return Automatable::describe_parameter (param); }