Merge branch 'master' into cairocanvas
[ardour.git] / libs / panners / 1in2out / panner_1in2out.cc
index 2851aec0957ea9fe3fc9814f5143ac958406fa90..2d5489f0ce9378700a283de3f11ae8c6ef3d2805 100644 (file)
 
 #include "ardour/session.h"
 #include "ardour/panner.h"
-#include "ardour/panner_1in2out.h"
 #include "ardour/utils.h"
 #include "ardour/audio_buffer.h"
 
+#include "ardour/debug.h"
 #include "ardour/runtime_functions.h"
 #include "ardour/buffer_set.h"
 #include "ardour/audio_buffer.h"
-#include "ardour/vbap.h"
+#include "ardour/pannable.h"
 
 #include "i18n.h"
+#include "panner_1in2out.h"
 
 #include "pbd/mathfix.h"
 
@@ -62,37 +63,81 @@ using namespace PBD;
 
 static PanPluginDescriptor _descriptor = {
         "Mono to Stereo Panner",
-        1, 1, 2, 2,
+        "http://ardour.org/plugin/panner_1in2out",
+        "http://ardour.org/plugin/panner_1in2out#ui",
+        1, 2, 
+        10000,
         Panner1in2out::factory
 };
 
-extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; }
+extern "C" ARDOURPANNER_API PanPluginDescriptor*  panner_descriptor () { return &_descriptor; }
 
-Panner1in2out::Panner1in2out (PannerShell& p)
+Panner1in2out::Panner1in2out (boost::shared_ptr<Pannable> p)
        : Panner (p)
-        , _position (new PanControllable (parent.session(), _("position"), this, Evoral::Parameter(PanAzimuthAutomation, 0, 0)))
-       , left (0.5)
-       , right (0.5)
-       , left_interp (left)
-       , right_interp (right)
 {
-        desired_left = left;
-        desired_right = right;
+        if (!_pannable->has_state()) {
+                _pannable->pan_azimuth_control->set_value (0.5);
+        }
+        
+        update ();
+
+        left = desired_left;
+        right = desired_right;
+        left_interp = left;
+        right_interp = right;
+
+        _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Panner1in2out::update, this));
 }
 
 Panner1in2out::~Panner1in2out ()
 {
 }
 
+void
+Panner1in2out::update ()
+{
+        float panR, panL;
+        float const pan_law_attenuation = -3.0f;
+        float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
+
+        panR = _pannable->pan_azimuth_control->get_value();
+        panL = 1 - panR;
+
+        desired_left = panL * (scale * panL + 1.0f - scale);
+        desired_right = panR * (scale * panR + 1.0f - scale);
+}
+
 void
 Panner1in2out::set_position (double p)
 {
-        _desired_right = p;
-        _desired_left = 1 - p;
+        if (clamp_position (p)) {
+                _pannable->pan_azimuth_control->set_value (p);
+        }
+}
+
+bool
+Panner1in2out::clamp_position (double& p)
+{
+        /* any position between 0.0 and 1.0 is legal */
+        DEBUG_TRACE (DEBUG::Panning, string_compose ("want to move panner to %1 - always allowed in 0.0-1.0 range\n", p));
+        p = max (min (p, 1.0), 0.0);
+        return true;
+}
+
+pair<double, double>
+Panner1in2out::position_range () const
+{
+       return make_pair (0, 1);
+}
+
+double 
+Panner1in2out::position () const
+{
+        return _pannable->pan_azimuth_control->get_value ();
 }
 
 void
-Panner1in2out::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t /* not used */)
+Panner1in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t /* not used */)
 {
        assert (obufs.count().n_audio() == 2);
 
@@ -100,17 +145,13 @@ Panner1in2out::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t
        Sample* dst;
        pan_t pan;
 
-       if (_muted) {
-               return;
-       }
-
        Sample* const src = srcbuf.data();
         
        /* LEFT OUTPUT */
 
        dst = obufs.get_audio(0).data();
 
-       if (fabsf ((delta = (left[which] - desired_left[which]))) > 0.002) { // about 1 degree of arc
+       if (fabsf ((delta = (left - desired_left))) > 0.002) { // about 1 degree of arc
 
                /* we've moving the pan by an appreciable amount, so we must
                   interpolate over 64 frames or nframes, whichever is smaller */
@@ -121,23 +162,23 @@ Panner1in2out::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t
                delta = -(delta / (float) (limit));
 
                for (n = 0; n < limit; n++) {
-                       left_interp[which] = left_interp[which] + delta;
-                       left = left_interp[which] + 0.9 * (left[which] - left_interp[which]);
-                       dst[n] += src[n] * left[which] * gain_coeff;
+                       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[which] * gain_coeff;
+               pan = left * gain_coeff;
 
                mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
 
        } else {
 
-               left[which] = desired_left[which];
-               left_interp[which] = left[which];
+               left = desired_left;
+               left_interp = left;
 
-               if ((pan = (left[which] * gain_coeff)) != 1.0f) {
+               if ((pan = (left * gain_coeff)) != 1.0f) {
 
                        if (pan != 0.0f) {
 
@@ -165,7 +206,7 @@ Panner1in2out::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t
 
        dst = obufs.get_audio(1).data();
 
-       if (fabsf ((delta = (right[which] - desired_right[which]))) > 0.002) { // about 1 degree of arc
+       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 */
@@ -176,14 +217,14 @@ Panner1in2out::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t
                delta = -(delta / (float) (limit));
 
                for (n = 0; n < limit; n++) {
-                       right_interp[which] = right_interp[which] + delta;
-                       right[which] = right_interp[which] + 0.9 * (right[which] - right_interp[which]);
-                       dst[n] += src[n] * right[which] * gain_coeff;
+                       right_interp = right_interp + delta;
+                       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[which] * gain_coeff;
+               pan = right * gain_coeff;
 
                mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
 
@@ -191,10 +232,10 @@ Panner1in2out::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t
 
        } else {
 
-               right[which] = desired_right[which];
-               right_interp[which] = right[which];
+               right = desired_right;
+               right_interp = right;
 
-               if ((pan = (right[which] * gain_coeff)) != 1.0f) {
+               if ((pan = (right * gain_coeff)) != 1.0f) {
 
                        if (pan != 0.0f) {
 
@@ -217,19 +258,140 @@ Panner1in2out::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t
 
 }
 
+void
+Panner1in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
+                                         framepos_t start, framepos_t end, pframes_t nframes,
+                                         pan_t** buffers, uint32_t which)
+{
+       assert (obufs.count().n_audio() == 2);
+
+       Sample* dst;
+       pan_t* pbuf;
+       Sample* const src = srcbuf.data();
+        pan_t* const position = buffers[0];
+
+       /* fetch positional data */
+
+       if (!_pannable->pan_azimuth_control->list()->curve().rt_safe_get_vector (start, end, position, nframes)) {
+               /* fallback */
+                distribute_one (srcbuf, obufs, 1.0, nframes, which);
+               return;
+       }
+
+       /* apply pan law to convert positional data into pan coefficients for
+          each buffer (output)
+       */
+
+       const float pan_law_attenuation = -3.0f;
+       const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
+
+       for (pframes_t n = 0; n < nframes; ++n) {
+
+                float panR = position[n];
+                const float panL = 1 - panR;
+
+                /* note that are overwriting buffers, but its OK
+                   because we're finished with their old contents
+                   (position automation data) and are
+                   replacing it with panning/gain coefficients 
+                   that we need to actually process the data.
+                */
+                
+                buffers[0][n] = panL * (scale * panL + 1.0f - scale);
+                buffers[1][n] = panR * (scale * panR + 1.0f - scale);
+        }
+
+       /* LEFT OUTPUT */
+
+       dst = obufs.get_audio(0).data();
+       pbuf = buffers[0];
+
+       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 OUTPUT */
+
+       dst = obufs.get_audio(1).data();
+       pbuf = buffers[1];
+
+       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 */
+}
+
+
+Panner*
+Panner1in2out::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> /* ignored */)
+{
+       return new Panner1in2out (p);
+}
+
+XMLNode&
+Panner1in2out::get_state ()
+{
+       XMLNode& root (Panner::get_state ());
+       root.add_property (X_("uri"), _descriptor.panner_uri);
+       /* this is needed to allow new sessions to load with old Ardour: */
+       root.add_property (X_("type"), _descriptor.name);
+       return root;
+}
+
+
+std::set<Evoral::Parameter> 
+Panner1in2out::what_can_be_automated() const
+{
+        set<Evoral::Parameter> s;
+        s.insert (Evoral::Parameter (PanAzimuthAutomation));
+        return s;
+}
+
 string
-Panner1in2out::describe_parameter (Evoral::Parameter param)
+Panner1in2out::describe_parameter (Evoral::Parameter p)
 {
-        switch (param.type()) {
-        case PanWidthAutomation:
-                return "Pan:width";
+        switch (p.type()) {
         case PanAzimuthAutomation:
-                return "Pan:position";
-        case PanElevationAutomation: 
-                error << X_("stereo panner should not have elevation control") << endmsg;
-                return "Pan:elevation";
-        } 
-        
-        return Automatable::describe_parameter (param);
+                return _("L/R");
+        default:
+                return _pannable->describe_parameter (p);
+        }
 }
 
+string 
+Panner1in2out::value_as_string (boost::shared_ptr<AutomationControl> ac) const
+{
+        /* DO NOT USE LocaleGuard HERE */
+        double val = ac->get_value();
+
+        switch (ac->parameter().type()) {
+        case PanAzimuthAutomation:
+                /* We show the position of the center of the image relative to the left & right.
+                   This is expressed as a pair of percentage values that ranges from (100,0) 
+                   (hard left) through (50,50) (hard center) to (0,100) (hard right).
+                   
+                   This is pretty wierd, but its the way audio engineers expect it. Just remember that
+                   the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
+
+                  This is designed to be as narrow as possible. Dedicated
+                  panner GUIs can do their own version of this if they need
+                  something less compact.
+                */
+                
+                return string_compose (_("L%1R%2"), (int) rint (100.0 * (1.0 - val)),
+                                       (int) rint (100.0 * val));
+                
+        default:
+                return _("unused");
+        }
+}
+
+void
+Panner1in2out::reset ()
+{
+       set_position (0.5);
+       update ();
+}