X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fpanner.cc;h=cb2ad8dd4a127aa04ed76869f311380eaa9d52c4;hb=0bee288b5d6c6ca1fb33b8bc3b584c6d2c3d3364;hp=3e8329cc9db58c593da506ea19e25764b1c0327b;hpb=0b572cdd84151335594965a3f0ed16f1665dfa56;p=ardour.git diff --git a/libs/ardour/panner.cc b/libs/ardour/panner.cc index 3e8329cc9d..cb2ad8dd4a 100644 --- a/libs/ardour/panner.cc +++ b/libs/ardour/panner.cc @@ -1,5 +1,6 @@ + /* - Copyright (C) 2004 Paul Davis + Copyright (C) 2004 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 @@ -15,9 +16,10 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - $Id$ */ +#include + #include #include #include @@ -27,23 +29,32 @@ #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 -#include -#include +#include "evoral/Curve.hpp" -#include -#include +#include "ardour/session.h" +#include "ardour/panner.h" +#include "ardour/utils.h" +#include "ardour/audio_buffer.h" + +#include "ardour/runtime_functions.h" +#include "ardour/buffer_set.h" +#include "ardour/audio_buffer.h" +#include "ardour/vbap.h" #include "i18n.h" -#include +#include "pbd/mathfix.h" using namespace std; using namespace ARDOUR; @@ -52,29 +63,26 @@ using namespace PBD; float Panner::current_automation_version_number = 1.0; string EqualPowerStereoPanner::name = "Equal Power Stereo"; -string Multi2dPanner::name = "Multiple (2D)"; -/* this is a default mapper of control values to a pan position - others can be imagined. +/* this is a default mapper of control values to a pan position + others can be imagined. */ -static pan_t direct_control_to_pan (double fract) { - return fract; -} - -static double direct_pan_to_control (pan_t val) { - return val; +static double direct_control_to_stereo_pan (double fract) +{ + return BaseStereoPanner::lr_fract_to_azimuth (fract); } -StreamPanner::StreamPanner (Panner& p) - : parent (p), - _control (*this) +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; - x = 0.5; - y = 0.5; - z = 0.5; + p.add_control (_control); } StreamPanner::~StreamPanner () @@ -82,29 +90,58 @@ StreamPanner::~StreamPanner () } void -StreamPanner::PanControllable::set_value (float val) +StreamPanner::set_mono (bool yn) { - panner.set_position (direct_control_to_pan (val)); + if (yn != _mono) { + _mono = yn; + StateChanged (); + } } -float -StreamPanner::PanControllable::get_value (void) const +double +StreamPanner::PanControllable::lower () const { - float xpos; - panner.get_effective_position (xpos); - return direct_pan_to_control (xpos); + switch (parameter().id()) { + case 200: /* width */ + return -1.0; + default: + return 0.0; + } } -bool -StreamPanner::PanControllable::can_send_feedback () const +void +StreamPanner::PanControllable::set_value (double 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 +StreamPanner::PanControllable::get_value (void) const { - AutoState astate = panner.get_parent().automation_state (); - - if ((astate == Play) || (astate == Touch && !panner.get_parent().touching())) { - return true; - } - - return false; + return AutomationControl::get_value(); } void @@ -117,157 +154,113 @@ StreamPanner::set_muted (bool yn) } void -StreamPanner::set_position (float xpos, bool link_call) -{ - if (!link_call && parent.linked()) { - parent.set_position (xpos, *this); - } - - if (x != xpos) { - x = xpos; - update (); - Changed (); - _control.Changed (); - } -} - -void -StreamPanner::set_position (float xpos, float ypos, bool link_call) -{ - if (!link_call && parent.linked()) { - parent.set_position (xpos, ypos, *this); - } - - if (x != xpos || y != ypos) { - - x = xpos; - y = ypos; - update (); - Changed (); - } -} - -void -StreamPanner::set_position (float xpos, float ypos, float zpos, bool link_call) +StreamPanner::set_position (const AngularVector& av, bool link_call) { if (!link_call && parent.linked()) { - parent.set_position (xpos, ypos, zpos, *this); + parent.set_position (av, *this); } - if (x != xpos || y != ypos || z != zpos) { - x = xpos; - y = ypos; - z = zpos; + if (_angles != av) { + _angles = av; update (); Changed (); + _control->Changed (); } } int -StreamPanner::set_state (const XMLNode& node) +StreamPanner::set_state (const XMLNode& node, int version) { const XMLProperty* prop; XMLNodeConstIterator iter; if ((prop = node.property (X_("muted")))) { - set_muted (prop->value() == "yes"); + set_muted (string_is_affirmative (prop->value())); } - return 0; -} - -void -StreamPanner::add_state (XMLNode& node) -{ - node.add_property (X_("muted"), (muted() ? "yes" : "no")); -} + if ((prop = node.property (X_("mono")))) { + 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); + } + } + } -BaseStereoPanner::BaseStereoPanner (Panner& p) - : StreamPanner (p), _automation (0.0, 1.0, 0.5) -{ + return 0; } -BaseStereoPanner::~BaseStereoPanner () +XMLNode& +StreamPanner::get_state () { + 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 -BaseStereoPanner::snapshot (jack_nframes_t now) +StreamPanner::distribute (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes) { - if (_automation.automation_state() == Write || _automation.automation_state() == Touch) { - _automation.rt_add (now, x); + if (_mono) { + /* we're in mono mode, so just pan the input to all outputs equally */ + int const N = parent.nouts (); + for (int i = 0; i < N; ++i) { + mix_buffers_with_gain (obufs.get_audio(i).data(), src.data(), nframes, gain_coeff); + } + } else { + /* normal mode, call the `real' distribute method */ + do_distribute (src, obufs, gain_coeff, nframes); } } void -BaseStereoPanner::transport_stopped (jack_nframes_t frame) -{ - _automation.reposition_for_rt_add (frame); - - if (_automation.automation_state() != Off) { - - if (_automation.automation_write()) { - _automation.save_state (_("automation write pass")); +StreamPanner::distribute_automated (AudioBuffer& src, BufferSet& obufs, + 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 */ + int const N = parent.nouts (); + for (int i = 0; i < N; ++i) { + mix_buffers_with_gain (obufs.get_audio(i).data(), src.data(), nframes, 1.0); } - - set_position (_automation.eval (frame)); + } else { + /* normal mode, call the `real' distribute method */ + do_distribute_automated (src, obufs, start, end, nframes, buffers); } + } -void -BaseStereoPanner::set_automation_style (AutoStyle style) -{ - _automation.set_automation_style (style); -} -void -BaseStereoPanner::set_automation_state (AutoState state) -{ - if (state != _automation.automation_state()) { +/*---------------------------------------------------------------------- */ - _automation.set_automation_state (state); - - if (state != Off) { - set_position (_automation.eval (parent.session().transport_frame())); - } - } +BaseStereoPanner::BaseStereoPanner (Panner& p, Evoral::Parameter param) + : StreamPanner (p, param) + , left (0.5) + , right (0.5) + , left_interp (left) + , right_interp (right) +{ } -int -BaseStereoPanner::save (ostream& out) const +BaseStereoPanner::~BaseStereoPanner () { - LocaleGuard lg (X_("POSIX")); - - /* force a single format for numeric data to ease session interchange - across national boundaries. - */ - - out << "begin" << endl; - - for (AutomationList::const_iterator i = _automation.const_begin(); i != _automation.const_end(); ++i) { - out << '\t' << (jack_nframes_t) floor ((*i)->when) << ' ' << (*i)->value << endl; - if (!out) { - error << string_compose (_("error writing pan automation file (%s)"), strerror (errno)) << endmsg; - return -1; - } - } - out << "end" << endl; - - return 0; } - + int BaseStereoPanner::load (istream& in, string path, uint32_t& linecnt) { char line[128]; LocaleGuard lg (X_("POSIX")); - - _automation.clear (); + + _control->list()->clear (); while (in.getline (line, sizeof (line), '\n')) { - jack_nframes_t when; + framepos_t when; double value; ++linecnt; @@ -276,26 +269,25 @@ 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; } - _automation.add (when, value, true); + _control->list()->fast_simple_add (when, value); } /* now that we are done loading */ - _automation.save_state (_("loaded from disk")); - _automation.StateChanged (Change (0)); + ((AutomationList*)_control->list().get())->StateChanged (); return 0; } void -BaseStereoPanner::distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, jack_nframes_t nframes) +BaseStereoPanner::do_distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes) { - assert(obufs.count().get(DataType::AUDIO) == 2); + assert(obufs.count().n_audio() == 2); pan_t delta; Sample* dst; @@ -304,69 +296,77 @@ BaseStereoPanner::distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain if (_muted) { return; } - - Sample* const src = srcbuf.data(nframes); + + Sample* const src = srcbuf.data(); /* LEFT */ - dst = obufs.get_audio(0).data(nframes); + dst = obufs.get_audio(0).data(); + + if (fabsf ((delta = (left - desired_left))) > 0.002) { // about 1 degree of arc - if (fabsf ((delta = (left - desired_left))) > 0.002) { // about 1 degree of arc - - /* interpolate over 64 frames or nframes, whichever is smaller */ - - jack_nframes_t limit = min ((jack_nframes_t)64, nframes); - jack_nframes_t n; + /* we've moving the pan by an appreciable amount, so we must + interpolate over 64 frames or nframes, whichever is smaller */ + + pframes_t const limit = min ((pframes_t) 64, nframes); + pframes_t n; delta = -(delta / (float) (limit)); - + for (n = 0; n < limit; n++) { left_interp = left_interp + delta; left = left_interp + 0.9 * (left - left_interp); dst[n] += src[n] * left * gain_coeff; } - + + /* then pan the rest of the buffer; no need for interpolation for this bit */ + pan = left * gain_coeff; - Session::mix_buffers_with_gain(dst+n,src+n,nframes-n,pan); - + mix_buffers_with_gain (dst+n,src+n,nframes-n,pan); + } else { - + left = desired_left; left_interp = left; if ((pan = (left * gain_coeff)) != 1.0f) { - + if (pan != 0.0f) { - - Session::mix_buffers_with_gain(dst,src,nframes,pan); + + /* pan is 1 but also not 0, so we must do it "properly" */ + + mix_buffers_with_gain(dst,src,nframes,pan); /* mark that we wrote into the buffer */ // obufs[0] = 0; - } - + } + } else { - - Session::mix_buffers_no_gain(dst,src,nframes); - + + /* pan is 1 so we can just copy the input samples straight in */ + + mix_buffers_no_gain(dst,src,nframes); + /* mark that we wrote into the buffer */ - + // obufs[0] = 0; } } /* RIGHT */ - dst = obufs.get_audio(1).data(nframes); - - if (fabsf ((delta = (right - desired_right))) > 0.002) { // about 1 degree of arc - - /* interpolate over 64 frames or nframes, whichever is smaller */ - - jack_nframes_t limit = min ((jack_nframes_t)64, nframes); - jack_nframes_t n; + dst = obufs.get_audio(1).data(); + + if (fabsf ((delta = (right - desired_right))) > 0.002) { // about 1 degree of arc + + /* we're moving the pan by an appreciable amount, so we must + interpolate over 64 frames or nframes, whichever is smaller */ + + pframes_t const limit = min ((pframes_t) 64, nframes); + pframes_t n; delta = -(delta / (float) (limit)); @@ -375,31 +375,37 @@ BaseStereoPanner::distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain right = right_interp + 0.9 * (right - right_interp); dst[n] += src[n] * right * gain_coeff; } - + + /* then pan the rest of the buffer, no need for interpolation for this bit */ + pan = right * gain_coeff; - - Session::mix_buffers_with_gain(dst+n,src+n,nframes-n,pan); - + + mix_buffers_with_gain(dst+n,src+n,nframes-n,pan); + /* XXX it would be nice to mark the buffer as written to */ } else { right = desired_right; right_interp = right; - + if ((pan = (right * gain_coeff)) != 1.0f) { - + if (pan != 0.0f) { + + /* pan is not 1 but also not 0, so we must do it "properly" */ - Session::mix_buffers_with_gain(dst,src,nframes,pan); - + mix_buffers_with_gain(dst,src,nframes,pan); + /* XXX it would be nice to mark the buffer as written to */ } - + } else { + + /* pan is 1 so we can just copy the input samples straight in */ - Session::mix_buffers_no_gain(dst,src,nframes); - + mix_buffers_no_gain(dst,src,nframes); + /* XXX it would be nice to mark the buffer as written to */ } } @@ -407,8 +413,8 @@ BaseStereoPanner::distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain /*---------------------------------------------------------------------- */ -EqualPowerStereoPanner::EqualPowerStereoPanner (Panner& p) - : BaseStereoPanner (p) +EqualPowerStereoPanner::EqualPowerStereoPanner (Panner& p, Evoral::Parameter param) + : BaseStereoPanner (p, param) { update (); @@ -426,54 +432,58 @@ void EqualPowerStereoPanner::update () { /* it would be very nice to split this out into a virtual function - that can be accessed from BaseStereoPanner and used in distribute_automated(). - - but the place where its used in distribute_automated() is a tight inner loop, + that can be accessed from BaseStereoPanner and used in do_distribute_automated(). + + but the place where its used in do_distribute_automated() is a tight inner loop, and making "nframes" virtual function calls to compute values is an absurd overhead. */ - /* x == 0 => hard left - x == 1 => hard right + /* x == 0 => hard left = 180.0 degrees + x == 1 => hard right = 0.0 degrees */ - float panR = x; - float panL = 1 - panR; + double _x = BaseStereoPanner::azimuth_to_lr_fract (_angles.azi); + + float const panR = _x; + float const panL = 1 - panR; + + float const pan_law_attenuation = -3.0f; + float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f); - const float pan_law_attenuation = -3.0f; - const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f); - desired_left = panL * (scale * panL + 1.0f - scale); desired_right = panR * (scale * panR + 1.0f - scale); - effective_x = x; + _effective_angles = _angles; + //_control->set_value(x); } void -EqualPowerStereoPanner::distribute_automated (AudioBuffer& srcbuf, BufferSet& obufs, - jack_nframes_t start, jack_nframes_t end, jack_nframes_t nframes, - pan_t** buffers) +EqualPowerStereoPanner::do_distribute_automated (AudioBuffer& srcbuf, BufferSet& obufs, + framepos_t start, framepos_t end, pframes_t nframes, + pan_t** buffers) { - assert(obufs.count().get(DataType::AUDIO) == 2); + assert (obufs.count().n_audio() == 2); Sample* dst; pan_t* pbuf; - Sample* const src = srcbuf.data(nframes); + Sample* const src = srcbuf.data(); /* fetch positional data */ - if (!_automation.rt_safe_get_vector (start, end, buffers[0], nframes)) { + if (!_control->list()->curve().rt_safe_get_vector (start, end, buffers[0], nframes)) { /* fallback */ if (!_muted) { - distribute (srcbuf, obufs, 1.0, nframes); + do_distribute (srcbuf, obufs, 1.0, nframes); } return; } /* store effective pan position. do this even if we are muted */ - if (nframes > 0) - effective_x = buffers[0][nframes-1]; + if (nframes > 0) { + _effective_angles.azi = BaseStereoPanner::lr_fract_to_azimuth (buffers[0][nframes-1]); + } if (_muted) { return; @@ -486,42 +496,42 @@ EqualPowerStereoPanner::distribute_automated (AudioBuffer& srcbuf, BufferSet& ob const float pan_law_attenuation = -3.0f; const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f); - for (jack_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; - float panR = buffers[0][n]; - float panL = 1 - panR; - buffers[0][n] = panL * (scale * panL + 1.0f - scale); buffers[1][n] = panR * (scale * panR + 1.0f - scale); } /* LEFT */ - dst = obufs.get_audio(0).data(nframes); + dst = obufs.get_audio(0).data(); pbuf = buffers[0]; - - for (jack_nframes_t n = 0; n < nframes; ++n) { + + for (pframes_t n = 0; n < nframes; ++n) { dst[n] += src[n] * pbuf[n]; - } + } /* XXX it would be nice to mark the buffer as written to */ /* RIGHT */ - dst = obufs.get_audio(1).data(nframes); + dst = obufs.get_audio(1).data(); pbuf = buffers[1]; - for (jack_nframes_t n = 0; n < nframes; ++n) { + for (pframes_t n = 0; n < nframes; ++n) { dst[n] += src[n] * pbuf[n]; - } - + } + /* XXX it would be nice to mark the buffer as written to */ } StreamPanner* -EqualPowerStereoPanner::factory (Panner& parent) +EqualPowerStereoPanner::factory (Panner& parent, Evoral::Parameter param, Speakers& /* ignored */) { - return new EqualPowerStereoPanner (parent); + return new EqualPowerStereoPanner (parent, param); } XMLNode& @@ -531,664 +541,381 @@ EqualPowerStereoPanner::get_state (void) } XMLNode& -EqualPowerStereoPanner::state (bool full_state) +EqualPowerStereoPanner::state (bool /*full_state*/) { - XMLNode* root = new XMLNode ("StreamPanner"); - char buf[64]; - LocaleGuard lg (X_("POSIX")); - - snprintf (buf, sizeof (buf), "%.12g", x); - root->add_property (X_("x"), buf); - root->add_property (X_("type"), EqualPowerStereoPanner::name); - if (full_state) { - snprintf (buf, sizeof (buf), "0x%x", _automation.automation_state()); - } else { - /* never store automation states other than off in a template */ - snprintf (buf, sizeof (buf), "0x%x", ARDOUR::Off); - } - root->add_property (X_("automation-state"), buf); - snprintf (buf, sizeof (buf), "0x%x", _automation.automation_style()); - root->add_property (X_("automation-style"), buf); - - StreamPanner::add_state (*root); - - return *root; + XMLNode& root (StreamPanner::get_state ()); + root.add_property (X_("type"), EqualPowerStereoPanner::name); + return root; } int -EqualPowerStereoPanner::set_state (const XMLNode& node) +EqualPowerStereoPanner::set_state (const XMLNode& node, int version) { - const XMLProperty* prop; - int x; - float pos; LocaleGuard lg (X_("POSIX")); - if ((prop = node.property (X_("x")))) { - pos = atof (prop->value().c_str()); - set_position (pos, true); - } + StreamPanner::set_state (node, version); - if ((prop = node.property (X_("automation-state")))) { - sscanf (prop->value().c_str(), "0x%x", &x); - _automation.set_automation_state ((AutoState) x); + for (XMLNodeConstIterator iter = node.children().begin(); iter != node.children().end(); ++iter) { - if (x != Off) { - set_position (_automation.eval (parent.session().transport_frame())); + 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())); + set_position (AngularVector (degrees, 0.0)); + } } } - if ((prop = node.property (X_("automation-style")))) { - sscanf (prop->value().c_str(), "0x%x", &x); - _automation.set_automation_style ((AutoStyle) x); - } - - StreamPanner::set_state (node); - return 0; } -/*----------------------------------------------------------------------*/ - -Multi2dPanner::Multi2dPanner (Panner& p) - : StreamPanner (p), _automation (0.0, 1.0, 0.5) // XXX useless +Panner::Panner (string name, Session& s) + : SessionObject (s, name) + , Automatable (s) { - update (); -} + //set_name_old_auto (name); + set_name (name); -Multi2dPanner::~Multi2dPanner () -{ + _linked = false; + _link_direction = SameDirection; + _bypassed = false; + _mono = false; } -void -Multi2dPanner::snapshot (jack_nframes_t now) +Panner::~Panner () { - // how? } void -Multi2dPanner::transport_stopped (jack_nframes_t frame) +Panner::set_linked (bool yn) { - //what? + if (yn != _linked) { + _linked = yn; + _session.set_dirty (); + LinkStateChanged (); /* EMIT SIGNAL */ + } } void -Multi2dPanner::set_automation_style (AutoStyle style) +Panner::set_link_direction (LinkDirection ld) { - //what? + if (ld != _link_direction) { + _link_direction = ld; + _session.set_dirty (); + LinkStateChanged (); /* EMIT SIGNAL */ + } } + void -Multi2dPanner::set_automation_state (AutoState state) +Panner::set_bypassed (bool yn) { - // what? + if (yn != _bypassed) { + _bypassed = yn; + StateChanged (); + } } + void -Multi2dPanner::update () +Panner::reset_to_default () { - static const float BIAS = FLT_MIN; - uint32_t i; - uint32_t nouts = parent.outputs.size(); - float dsq[nouts]; - float f, fr; - vector pans; + vector positions; - f = 0.0f; + switch (outputs.size()) { + case 0: + case 1: + return; + } - for (i = 0; i < nouts; i++) { - dsq[i] = ((x - parent.outputs[i].x) * (x - parent.outputs[i].x) + (y - parent.outputs[i].y) * (y - parent.outputs[i].y) + BIAS); - if (dsq[i] < 0.0) { - dsq[i] = 0.0; + if (outputs.size() == 2) { + AngularVector a; + switch (_streampanners.size()) { + case 1: + 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 */ + _streampanners.front()->set_position (a); + _streampanners.front()->pan_control()->list()->reset_default (0.0); + a.azi = 0.0; /* "right", in degrees */ + _streampanners.back()->set_position (a); + _streampanners.back()->pan_control()->list()->reset_default (1.0); + return; + default: + break; } - f += dsq[i] * dsq[i]; - } -#ifdef __APPLE__ - // terrible hack to support OSX < 10.3.9 builds - fr = (float) (1.0 / sqrt((double)f)); -#else - fr = 1.0 / sqrtf(f); -#endif - for (i = 0; i < nouts; ++i) { - parent.outputs[i].desired_pan = 1.0f - (dsq[i] * fr); } - effective_x = x; + vector::iterator o; + vector::iterator p; + + for (o = outputs.begin(), p = _streampanners.begin(); o != outputs.end() && p != _streampanners.end(); ++o, ++p) { + (*p)->set_position ((*o).position); + } } void -Multi2dPanner::distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, jack_nframes_t nframes) +Panner::reset_streampanner (uint32_t which) { - Sample* dst; - pan_t pan; - vector::iterator o; - uint32_t n; + AngularVector a; - if (_muted) { + if (which >= _streampanners.size() || which >= outputs.size()) { return; } - - Sample* const src = srcbuf.data(nframes); - - - for (n = 0, o = parent.outputs.begin(); o != parent.outputs.end(); ++o, ++n) { - - dst = obufs.get_audio(n).data(nframes); - -#ifdef CAN_INTERP - if (fabsf ((delta = (left_interp - desired_left))) > 0.002) { // about 1 degree of arc - - /* interpolate over 64 frames or nframes, whichever is smaller */ - - jack_nframes_t limit = min ((jack_nframes_t)64, nframes); - jack_nframes_t n; - - delta = -(delta / (float) (limit)); - - for (n = 0; n < limit; n++) { - left_interp = left_interp + delta; - left = left_interp + 0.9 * (left - left_interp); - dst[n] += src[n] * left * gain_coeff; - } - - pan = left * gain_coeff; - - for (; n < nframes; ++n) { - dst[n] += src[n] * pan; - } - - } else { -#else - pan = (*o).desired_pan; - - if ((pan *= gain_coeff) != 1.0f) { - - if (pan != 0.0f) { - - for (jack_nframes_t n = 0; n < nframes; ++n) { - dst[n] += src[n] * pan; - } - - } + switch (outputs.size()) { + case 0: + case 1: + return; - + case 2: + switch (_streampanners.size()) { + case 1: + /* stereo out, 1 stream, default = middle */ + 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 { - - for (jack_nframes_t n = 0; n < nframes; ++n) { - dst[n] += src[n]; - } - + a.azi = 0.0; /* "right", in degrees */ + _streampanners.back()->set_position (a); + _streampanners.back()->pan_control()->list()->reset_default (1.0); } -#endif -#ifdef CAN_INTERP + break; } -#endif + return; + + default: + _streampanners[which]->set_position (outputs[which].position); } - - return; } +/** + * Reset the panner with a given number of outs and panners (and hence inputs) + * + * \param nouts Number of outputs. + * \param npans Number of panners. + */ void -Multi2dPanner::distribute_automated (AudioBuffer& src, BufferSet& obufs, - jack_nframes_t start, jack_nframes_t end, jack_nframes_t nframes, - pan_t** buffers) +Panner::reset (uint32_t nouts, uint32_t npans) { - if (_muted) { + uint32_t n; + bool changed = false; + bool do_not_and_did_not_need_panning = ((nouts < 2) && (outputs.size() < 2)); + + /* if new and old config don't need panning, or if + the config hasn't changed, we're done. + */ + + if (do_not_and_did_not_need_panning || + ((nouts == outputs.size()) && (npans == _streampanners.size()))) { return; } - /* what ? */ + n = _streampanners.size(); + clear_panners (); - return; -} + if (n != npans) { + changed = true; + } -StreamPanner* -Multi2dPanner::factory (Panner& p) -{ - return new Multi2dPanner (p); -} + n = outputs.size(); + outputs.clear (); -int -Multi2dPanner::load (istream& in, string path, uint32_t& linecnt) -{ - return 0; -} + if (n != nouts) { + changed = true; + } -int -Multi2dPanner::save (ostream& out) const -{ - return 0; -} - -XMLNode& -Multi2dPanner::get_state (void) -{ - return state (true); -} - -XMLNode& -Multi2dPanner::state (bool full_state) -{ - XMLNode* root = new XMLNode ("StreamPanner"); - char buf[64]; - LocaleGuard lg (X_("POSIX")); - - snprintf (buf, sizeof (buf), "%.12g", x); - root->add_property (X_("x"), buf); - snprintf (buf, sizeof (buf), "%.12g", y); - root->add_property (X_("y"), buf); - root->add_property (X_("type"), Multi2dPanner::name); - - return *root; -} - -int -Multi2dPanner::set_state (const XMLNode& node) -{ - const XMLProperty* prop; - float newx,newy; - LocaleGuard lg (X_("POSIX")); - - newx = -1; - newy = -1; - - if ((prop = node.property (X_("x")))) { - newx = atof (prop->value().c_str()); - } - - if ((prop = node.property (X_("y")))) { - newy = atof (prop->value().c_str()); - } - - if (x < 0 || y < 0) { - error << _("badly-formed positional data for Multi2dPanner - ignored") - << endmsg; - return -1; - } - - set_position (newx, newy); - return 0; -} - -/*---------------------------------------------------------------------- */ - -Panner::Panner (string name, Session& s) - : _session (s) -{ - set_name (name); - _linked = false; - _link_direction = SameDirection; - _bypassed = false; -} - -Panner::~Panner () -{ -} - -void -Panner::set_linked (bool yn) -{ - if (yn != _linked) { - _linked = yn; - _session.set_dirty (); - LinkStateChanged (); /* EMIT SIGNAL */ - } -} - -void -Panner::set_link_direction (LinkDirection ld) -{ - if (ld != _link_direction) { - _link_direction = ld; - _session.set_dirty (); - LinkStateChanged (); /* EMIT SIGNAL */ - } -} - -void -Panner::set_name (string str) -{ - automation_path = _session.automation_dir(); - automation_path += _session.snap_name(); - automation_path += "-pan-"; - automation_path += legalize_for_path (str); - automation_path += ".automation"; -} - - -void -Panner::set_bypassed (bool yn) -{ - if (yn != _bypassed) { - _bypassed = yn; - StateChanged (); - } -} - - -void -Panner::reset (uint32_t nouts, uint32_t npans) -{ - uint32_t n; - bool changed = false; - - - if (nouts < 2 || (nouts == outputs.size() && npans == size())) { - return; - } - - n = size(); - clear (); - - if (n != npans) { - changed = true; - } - - n = outputs.size(); - outputs.clear (); - - if (n != nouts) { - changed = true; - } + if (nouts < 2) { + /* no need for panning with less than 2 outputs */ + if (changed) { + Changed (); /* EMIT SIGNAL */ + } + return; + } switch (nouts) { case 0: + /* XXX: this can never happen */ break; case 1: + /* XXX: this can never happen */ fatal << _("programming error:") << X_("Panner::reset() called with a single output") << endmsg; /*NOTREACHED*/ break; - case 2: - /* line */ - outputs.push_back (Output (0, 0)); - outputs.push_back (Output (1.0, 0)); - - for (n = 0; n < npans; ++n) { - push_back (new EqualPowerStereoPanner (*this)); - } - break; - - case 3: // triangle - outputs.push_back (Output (0.5, 0)); - outputs.push_back (Output (0, 1.0)); - outputs.push_back (Output (1.0, 1.0)); - - for (n = 0; n < npans; ++n) { - push_back (new Multi2dPanner (*this)); - } - - break; - - case 4: // square - outputs.push_back (Output (0, 0)); - outputs.push_back (Output (1.0, 0)); - outputs.push_back (Output (1.0, 1.0)); - outputs.push_back (Output (0, 1.0)); - - for (n = 0; n < npans; ++n) { - push_back (new Multi2dPanner (*this)); - } - - break; - - case 5: //square+offcenter center - outputs.push_back (Output (0, 0)); - outputs.push_back (Output (1.0, 0)); - outputs.push_back (Output (1.0, 1.0)); - outputs.push_back (Output (0, 1.0)); - outputs.push_back (Output (0.5, 0.75)); - + case 2: // line + outputs.push_back (Output (AngularVector (180.0, 0.0))); + outputs.push_back (Output (AngularVector (0.0, 0,0))); for (n = 0; n < npans; ++n) { - push_back (new Multi2dPanner (*this)); + _streampanners.push_back (new EqualPowerStereoPanner (*this, Evoral::Parameter(PanAutomation, 0, n))); } - break; default: - /* XXX horrible placement. FIXME */ - for (n = 0; n < nouts; ++n) { - outputs.push_back (Output (0.1 * n, 0.1 * n)); - } - + setup_speakers (nouts); for (n = 0; n < npans; ++n) { - push_back (new Multi2dPanner (*this)); + _streampanners.push_back (new VBAPanner (*this, Evoral::Parameter(PanAutomation, 0, n), _session.get_speakers())); } - break; } - for (iterator x = begin(); x != end(); ++x) { + for (std::vector::iterator x = _streampanners.begin(); x != _streampanners.end(); ++x) { (*x)->update (); } - /* force hard left/right panning in a common case: 2in/2out + 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. */ + if (changed) { + Changed (); /* EMIT SIGNAL */ + } + + /* force hard left/right panning in a common case: 2in/2out + */ + if (npans == 2 && outputs.size() == 2) { /* Do this only if we changed configuration, or our configuration - appears to be the default set up (center). + appears to be the default set up (zero degrees) */ - float left; - float right; + AngularVector left; + AngularVector right; - front()->get_position (left); - back()->get_position (right); + left = _streampanners.front()->get_position (); + right = _streampanners.back()->get_position (); - if (changed || ((left == 0.5) && (right == 0.5))) { - - front()->set_position (0.0); - front()->automation().reset_default (0.0); - - back()->set_position (1.0); - back()->automation().reset_default (1.0); - - changed = true; + 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); + + _streampanners.back()->set_position (AngularVector (0.0, 0.0)); + _streampanners.back()->pan_control()->list()->reset_default (1.0); } - } - if (changed) { - Changed (); /* EMIT SIGNAL */ - } + } else if (npans > 1 && outputs.size() > 2) { + + /* 2d panning: spread signals equally around a circle */ + + 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 + */ - return; + 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; + } + } } void Panner::remove (uint32_t which) { vector::iterator i; - for (i = begin(); i != end() && which; ++i, --which); + for (i = _streampanners.begin(); i != _streampanners.end() && which; ++i, --which) {} - if (i != end()) { + if (i != _streampanners.end()) { delete *i; - erase (i); + _streampanners.erase (i); } } + +/** Remove all our StreamPanners */ void -Panner::clear () +Panner::clear_panners () { - for (vector::iterator i = begin(); i != end(); ++i) { + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { delete *i; } - vector::clear (); + _streampanners.clear (); } void Panner::set_automation_style (AutoStyle style) { - for (vector::iterator i = begin(); i != end(); ++i) { - (*i)->set_automation_style (style); + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { + ((AutomationList*)(*i)->pan_control()->list().get())->set_automation_style (style); } _session.set_dirty (); -} +} void Panner::set_automation_state (AutoState state) { - for (vector::iterator i = begin(); i != end(); ++i) { - (*i)->set_automation_state (state); + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { + ((AutomationList*)(*i)->pan_control()->list().get())->set_automation_state (state); } _session.set_dirty (); -} +} AutoState Panner::automation_state () const { + boost::shared_ptr l; if (!empty()) { - return front()->automation().automation_state (); - } else { - return Off; + boost::shared_ptr control = _streampanners.front()->pan_control(); + if (control) { + l = boost::dynamic_pointer_cast(control->list()); + } } + + return l ? l->automation_state() : Off; } AutoStyle Panner::automation_style () const { + boost::shared_ptr l; if (!empty()) { - return front()->automation().automation_style (); - } else { - return Absolute; - } -} - -void -Panner::transport_stopped (jack_nframes_t frame) -{ - for (vector::iterator i = begin(); i != end(); ++i) { - (*i)->transport_stopped (frame); - } -} - -void -Panner::snapshot (jack_nframes_t now) -{ - for (vector::iterator i = begin(); i != end(); ++i) { - (*i)->snapshot (now); - } -} - -void -Panner::clear_automation () -{ - for (vector::iterator i = begin(); i != end(); ++i) { - (*i)->automation().clear (); - } - _session.set_dirty (); -} - -int -Panner::save () const -{ - ofstream out (automation_path.c_str()); - - if (!out) { - error << string_compose (_("cannot open pan automation file \"%1\" for saving (%s)"), automation_path, strerror (errno)) - << endmsg; - return -1; - } - - out << X_("version ") << current_automation_version_number << endl; - - for (vector::const_iterator i = begin(); i != end(); ++i) { - if ((*i)->save (out)) { - return -1; + boost::shared_ptr control = _streampanners.front()->pan_control(); + if (control) { + l = boost::dynamic_pointer_cast(control->list()); } } - return 0; -} - -int -Panner::load () -{ - char line[128]; - uint32_t linecnt = 0; - float version; - iterator sp; - LocaleGuard lg (X_("POSIX")); - - if (automation_path.length() == 0) { - return 0; - } - - if (access (automation_path.c_str(), F_OK)) { - return 0; - } - - ifstream in (automation_path.c_str()); - - if (!in) { - error << string_compose (_("cannot open pan automation file %1 (%2)"), - automation_path, strerror (errno)) - << endmsg; - return -1; - } - - sp = begin(); - - while (in.getline (line, sizeof(line), '\n')) { - - if (++linecnt == 1) { - if (memcmp (line, X_("version"), 7) == 0) { - if (sscanf (line, "version %f", &version) != 1) { - error << string_compose(_("badly formed version number in pan automation event file \"%1\""), automation_path) << endmsg; - return -1; - } - } else { - error << string_compose(_("no version information in pan automation event file \"%1\" (first line = %2)"), - automation_path, line) << endmsg; - return -1; - } - - if (version != current_automation_version_number) { - error << string_compose(_("mismatched pan automation event file version (%1)"), version) << endmsg; - return -1; - } - - continue; - } - - if (strlen (line) == 0 || line[0] == '#') { - continue; - } - - if (strcmp (line, "begin") == 0) { - - if (sp == end()) { - error << string_compose (_("too many panner states found in pan automation file %1"), - automation_path) - << endmsg; - return -1; - } - - if ((*sp)->load (in, automation_path, linecnt)) { - return -1; - } - - ++sp; - } - } - - return 0; + return l ? l->automation_style() : Absolute; } struct PanPlugins { string name; uint32_t nouts; - StreamPanner* (*factory)(Panner&); + StreamPanner* (*factory)(Panner&, Evoral::Parameter, Speakers&); }; PanPlugins pan_plugins[] = { { EqualPowerStereoPanner::name, 2, EqualPowerStereoPanner::factory }, - { Multi2dPanner::name, 3, Multi2dPanner::factory }, + { VBAPanner::name, 3, VBAPanner::factory }, { string (""), 0, 0 } }; @@ -1201,108 +928,116 @@ Panner::get_state (void) XMLNode& Panner::state (bool full) { - XMLNode* root = new XMLNode (X_("Panner")); - char buf[32]; - - for (iterator p = begin(); p != end(); ++p) { - root->add_child_nocopy ((*p)->state (full)); - } + XMLNode* node = new XMLNode ("Panner"); - root->add_property (X_("linked"), (_linked ? "yes" : "no")); - snprintf (buf, sizeof (buf), "%d", _link_direction); - root->add_property (X_("link_direction"), buf); - root->add_property (X_("bypassed"), (bypassed() ? "yes" : "no")); + char buf[32]; - /* add each output */ + 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")); + snprintf (buf, sizeof (buf), "%zd", outputs.size()); + node->add_property (X_("outputs"), buf); - for (vector::iterator o = outputs.begin(); o != outputs.end(); ++o) { - XMLNode* onode = new XMLNode (X_("Output")); - snprintf (buf, sizeof (buf), "%.12g", (*o).x); - onode->add_property (X_("x"), buf); - snprintf (buf, sizeof (buf), "%.12g", (*o).y); - onode->add_property (X_("y"), buf); - root->add_child_nocopy (*onode); + for (vector::const_iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { + node->add_child_nocopy ((*i)->state (full)); } - if (full) { - if (save () == 0) { - root->add_property (X_("automation"), Glib::path_get_basename (automation_path)); - } - } + node->add_child_nocopy (get_automation_xml_state ()); - return *root; + return *node; } int -Panner::set_state (const XMLNode& node) +Panner::set_state (const XMLNode& node, int version) { - XMLNodeList nlist; + XMLNodeList nlist = node.children (); XMLNodeConstIterator niter; const XMLProperty *prop; uint32_t i; + uint32_t num_panners = 0; StreamPanner* sp; LocaleGuard lg (X_("POSIX")); - clear (); + clear_panners (); + + ChanCount ins = ChanCount::ZERO; + ChanCount outs = ChanCount::ZERO; + + // XXX: this might not be necessary anymore outputs.clear (); if ((prop = node.property (X_("linked"))) != 0) { - set_linked (prop->value() == "yes"); + set_linked (string_is_affirmative (prop->value())); } - if ((prop = node.property (X_("bypassed"))) != 0) { - set_bypassed (prop->value() == "yes"); + set_bypassed (string_is_affirmative (prop->value())); } if ((prop = node.property (X_("link_direction"))) != 0) { - sscanf (prop->value().c_str(), "%d", &i); - set_link_direction ((LinkDirection) (i)); - } - - nlist = node.children(); - - for (niter = nlist.begin(); niter != nlist.end(); ++niter) { - if ((*niter)->name() == X_("Output")) { - - float x, y; - - prop = (*niter)->property (X_("x")); - sscanf (prop->value().c_str(), "%g", &x); - - prop = (*niter)->property (X_("y")); - sscanf (prop->value().c_str(), "%g", &y); - - outputs.push_back (Output (x, y)); - } - } + LinkDirection ld; /* here to provide type information */ + set_link_direction (LinkDirection (string_2_enum (prop->value(), ld))); + } + + if ((prop = node.property (X_("outputs"))) != 0) { + uint32_t n = atoi (prop->value()); + + while (n--) { + AngularVector a; // value is irrelevant + outputs.push_back (Output (a)); + } + + } 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)); + } + } + } for (niter = nlist.begin(); niter != nlist.end(); ++niter) { if ((*niter)->name() == X_("StreamPanner")) { - + if ((prop = (*niter)->property (X_("type")))) { - + for (i = 0; pan_plugins[i].factory; ++i) { if (prop->value() == pan_plugins[i].name) { - - + + /* note that we assume that all the stream panners are of the same type. pretty good - assumption, but its still an assumption. + assumption, but it's still an assumption. */ - - sp = pan_plugins[i].factory (*this); - - if (sp->set_state (**niter) == 0) { - push_back (sp); - } - + + sp = pan_plugins[i].factory (*this, Evoral::Parameter(PanAutomation, 0, num_panners), _session.get_speakers ()); + num_panners++; + + 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()) @@ -1315,29 +1050,36 @@ Panner::set_state (const XMLNode& node) return -1; } - } + } } - /* don't try to load automation if it wasn't marked as existing */ + 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")))) { /* automation path is relative */ - - automation_path = _session.automation_dir(); - automation_path += prop->value (); - } - return 0; -} + automation_path = Glib::build_filename(_session.automation_dir(), prop->value ()); + } + for (niter = nlist.begin(); niter != nlist.end(); ++niter) { + if ((*niter)->name() == X_("Automation")) { + set_automation_xml_state (**niter, Evoral::Parameter (PanAutomation)); + } + } + return 0; +} bool Panner::touching () const { - for (vector::const_iterator i = begin(); i != end(); ++i) { - if ((*i)->automation().touching ()) { + for (vector::const_iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { + if (((AutomationList*)(*i)->pan_control()->list().get())->touching ()) { return true; } } @@ -1346,154 +1088,43 @@ Panner::touching () const } void -Panner::set_position (float xpos, StreamPanner& orig) +Panner::set_position (const AngularVector& a, StreamPanner& orig) { - float xnow; - float xdelta ; - float xnew; - - orig.get_position (xnow); - xdelta = xpos - xnow; - - if (_link_direction == SameDirection) { + AngularVector delta; + AngularVector new_position; - for (vector::iterator i = begin(); i != end(); ++i) { - if (*i == &orig) { - (*i)->set_position (xpos, true); - } else { - (*i)->get_position (xnow); - xnew = min (1.0f, xnow + xdelta); - xnew = max (0.0f, xnew); - (*i)->set_position (xnew, true); - } - } + delta = orig.get_position() - a; - } else { - - for (vector::iterator i = begin(); i != end(); ++i) { - if (*i == &orig) { - (*i)->set_position (xpos, true); - } else { - (*i)->get_position (xnow); - xnew = min (1.0f, xnow - xdelta); - xnew = max (0.0f, xnew); - (*i)->set_position (xnew, true); - } - } - } -} - -void -Panner::set_position (float xpos, float ypos, StreamPanner& orig) -{ - float xnow, ynow; - float xdelta, ydelta; - float xnew, ynew; - - orig.get_position (xnow, ynow); - xdelta = xpos - xnow; - ydelta = ypos - ynow; - if (_link_direction == SameDirection) { - for (vector::iterator i = begin(); i != end(); ++i) { + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { if (*i == &orig) { - (*i)->set_position (xpos, ypos, true); + (*i)->set_position (a, true); } else { - (*i)->get_position (xnow, ynow); - - xnew = min (1.0f, xnow + xdelta); - xnew = max (0.0f, xnew); - - ynew = min (1.0f, ynow + ydelta); - ynew = max (0.0f, ynew); - - (*i)->set_position (xnew, ynew, true); + new_position = (*i)->get_position() + delta; + (*i)->set_position (new_position, true); } } } else { - for (vector::iterator i = begin(); i != end(); ++i) { + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { if (*i == &orig) { - (*i)->set_position (xpos, ypos, true); + (*i)->set_position (a, true); } else { - (*i)->get_position (xnow, ynow); - - xnew = min (1.0f, xnow - xdelta); - xnew = max (0.0f, xnew); - - ynew = min (1.0f, ynow - ydelta); - ynew = max (0.0f, ynew); - - (*i)->set_position (xnew, ynew, true); + new_position = (*i)->get_position() - delta; + (*i)->set_position (new_position, true); } } } } void -Panner::set_position (float xpos, float ypos, float zpos, StreamPanner& orig) +Panner::distribute_no_automation (BufferSet& inbufs, BufferSet& outbufs, pframes_t nframes, gain_t gain_coeff) { - float xnow, ynow, znow; - float xdelta, ydelta, zdelta; - float xnew, ynew, znew; - - orig.get_position (xnow, ynow, znow); - xdelta = xpos - xnow; - ydelta = ypos - ynow; - zdelta = zpos - znow; - - if (_link_direction == SameDirection) { - - for (vector::iterator i = begin(); i != end(); ++i) { - if (*i == &orig) { - (*i)->set_position (xpos, ypos, zpos, true); - } else { - (*i)->get_position (xnow, ynow, znow); - - xnew = min (1.0f, xnow + xdelta); - xnew = max (0.0f, xnew); - - ynew = min (1.0f, ynow + ydelta); - ynew = max (0.0f, ynew); - - znew = min (1.0f, znow + zdelta); - znew = max (0.0f, znew); - - (*i)->set_position (xnew, ynew, znew, true); - } - } - - } else { - - for (vector::iterator i = begin(); i != end(); ++i) { - if (*i == &orig) { - (*i)->set_position (xpos, ypos, true); - } else { - (*i)->get_position (xnow, ynow, znow); - - xnew = min (1.0f, xnow - xdelta); - xnew = max (0.0f, xnew); - - ynew = min (1.0f, ynow - ydelta); - ynew = max (0.0f, ynew); - - znew = min (1.0f, znow + zdelta); - znew = max (0.0f, znew); - - (*i)->set_position (xnew, ynew, znew, true); - } - } - } -} - -void -Panner::distribute_no_automation (BufferSet& inbufs, BufferSet& outbufs, jack_nframes_t nframes, jack_nframes_t offset, gain_t gain_coeff) -{ - if (outbufs.count().get(DataType::AUDIO) == 0) { + if (outbufs.count().n_audio() == 0) { // Don't want to lose audio... - assert(inbufs.count().get(DataType::AUDIO) == 0); + assert(inbufs.count().n_audio() == 0); return; } @@ -1501,8 +1132,8 @@ Panner::distribute_no_automation (BufferSet& inbufs, BufferSet& outbufs, jack_nf assert(!bypassed()); assert(!empty()); - - if (outbufs.count().get(DataType::AUDIO) == 1) { + + if (outbufs.count().n_audio() == 1) { AudioBuffer& dst = outbufs.get_audio(0); @@ -1510,62 +1141,61 @@ Panner::distribute_no_automation (BufferSet& inbufs, BufferSet& outbufs, jack_nf /* only one output, and gain was zero, so make it silent */ - dst.silence(offset); - + dst.silence (nframes); + } else if (gain_coeff == 1.0f){ /* mix all buffers into the output */ // copy the first - dst.read_from(inbufs.get_audio(0), nframes, offset); - + 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.accumulate_from(*i, nframes, offset); + 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); + } } - //audio_output(0)->mark_silence (false); // FIXME - } else { /* mix all buffers into the output, scaling them all by the gain */ // copy the first - dst.read_from(inbufs.get_audio(0), nframes, offset); - + dst.read_from(inbufs.get_audio(0), nframes); + // accumulate (with gain) starting with the second - BufferSet::audio_iterator i = inbufs.audio_begin(); - for (++i; i != inbufs.audio_end(); ++i) { - dst.accumulate_with_gain_from(*i, nframes, offset, gain_coeff); + 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); + } } - //audio_output(0)->mark_silence (false); // FIXME } return; } - // More than 1 output, we should have 1 panner for each input - assert(size() == inbufs.count().get(DataType::AUDIO)); - /* the terrible silence ... */ for (BufferSet::audio_iterator i = outbufs.audio_begin(); i != outbufs.audio_end(); ++i) { - i->silence(nframes, offset); + i->silence(nframes); } BufferSet::audio_iterator i = inbufs.audio_begin(); - for (iterator pan = begin(); pan != end(); ++pan, ++i) { + + for (vector::iterator pan = _streampanners.begin(); pan != _streampanners.end() && i != inbufs.audio_end(); ++pan, ++i) { (*pan)->distribute (*i, outbufs, gain_coeff, nframes); } } void -Panner::distribute (BufferSet& inbufs, BufferSet& outbufs, jack_nframes_t start_frame, jack_nframes_t end_frame, jack_nframes_t nframes, jack_nframes_t offset) -{ - if (outbufs.count().get(DataType::AUDIO) == 0) { +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 - assert(inbufs.count().get(DataType::AUDIO) == 0); + assert(inbufs.count().n_audio() == 0); return; } @@ -1574,52 +1204,349 @@ Panner::distribute (BufferSet& inbufs, BufferSet& outbufs, jack_nframes_t start_ assert(!empty()); // If we shouldn't play automation defer to distribute_no_automation - if ( !( automation_state() & Play || - ((automation_state() & Touch) && !touching()) ) ) { + if (!(automation_state() & Play || ((automation_state() & Touch) && !touching()))) { // Speed quietning gain_t gain_coeff = 1.0; - if (fabsf(_session.transport_speed()) > 1.5f) { + + if (fabsf(_session.transport_speed()) > 1.5f && Config->get_quieten_at_speed ()) { gain_coeff = speed_quietning; } - distribute_no_automation(inbufs, outbufs, nframes, offset, gain_coeff); + distribute_no_automation (inbufs, outbufs, nframes, gain_coeff); return; } // Otherwise.. let the automation flow, baby - - if (outbufs.count().get(DataType::AUDIO) == 1) { + + if (outbufs.count().n_audio() == 1) { AudioBuffer& dst = outbufs.get_audio(0); // FIXME: apply gain automation? // copy the first - dst.read_from(inbufs.get_audio(0), nframes, offset); + 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.accumulate_from(*i, nframes, offset); + dst.merge_from(*i, nframes); } - //audio_output(0)->mark_silence (false); // FIXME - return; } // More than 1 output, we should have 1 panner for each input - assert(size() == inbufs.count().get(DataType::AUDIO)); - + //assert(_streampanners.size() == inbufs.count().n_audio()); + /* the terrible silence ... */ for (BufferSet::audio_iterator i = outbufs.audio_begin(); i != outbufs.audio_end(); ++i) { - i->silence(nframes, offset); + i->silence(nframes); } BufferSet::audio_iterator i = inbufs.audio_begin(); - for (iterator pan = begin(); pan != end(); ++pan, ++i) { + for (vector::iterator pan = _streampanners.begin(); pan != _streampanners.end(); ++pan, ++i) { (*pan)->distribute_automated (*i, outbufs, start_frame, end_frame, nframes, _session.pan_automation_buffer()); } } +/* old school automation handling */ + +/* +void +Panner::set_name (string str) +{ + automation_path = Glib::build_filename(_session.automation_dir(), + _session.snap_name() + "-pan-" + legalize_for_path (str) + ".automation"); +} +*/ + +int +Panner::load () +{ + char line[128]; + uint32_t linecnt = 0; + float version; + vector::iterator sp; + LocaleGuard lg (X_("POSIX")); + + if (automation_path.length() == 0) { + return 0; + } + + if (access (automation_path.c_str(), F_OK)) { + return 0; + } + + ifstream in (automation_path.c_str()); + + if (!in) { + error << string_compose (_("cannot open pan automation file %1 (%2)"), + automation_path, strerror (errno)) + << endmsg; + return -1; + } + + sp = _streampanners.begin(); + + while (in.getline (line, sizeof(line), '\n')) { + + if (++linecnt == 1) { + if (memcmp (line, X_("version"), 7) == 0) { + if (sscanf (line, "version %f", &version) != 1) { + error << string_compose(_("badly formed version number in pan automation event file \"%1\""), automation_path) << endmsg; + return -1; + } + } else { + error << string_compose(_("no version information in pan automation event file \"%1\" (first line = %2)"), + automation_path, line) << endmsg; + return -1; + } + + continue; + } + + if (strlen (line) == 0 || line[0] == '#') { + continue; + } + + if (strcmp (line, "begin") == 0) { + + if (sp == _streampanners.end()) { + error << string_compose (_("too many panner states found in pan automation file %1"), + automation_path) + << endmsg; + return -1; + } + + if ((*sp)->load (in, automation_path, linecnt)) { + return -1; + } + + ++sp; + } + } + + return 0; +} + +void +Panner::set_mono (bool yn) +{ + if (yn != _mono) { + _mono = yn; + StateChanged (); + } + + for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { + (*i)->set_mono (yn); + } +} + +string +Panner::value_as_string (double v) +{ + if (Panner::equivalent (v, 0.5)) { + return _("C"); + } else if (equivalent (v, 0)) { + return _("L"); + } else if (equivalent (v, 1)) { + return _("R"); + } else if (v < 0.5) { + stringstream s; + s << fixed << setprecision (0) << _("L") << ((0.5 - v) * 200) << "%"; + return s.str(); + } else { + stringstream s; + s << fixed << setprecision (0) << _("R") << ((v -0.5) * 200) << "%"; + return s.str (); + } + + return ""; +} + +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; + + 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; + } + 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); + } + + 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); + } + + 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); +}