globally change all use of "frame" to refer to audio into "sample".
[ardour.git] / libs / panners / 2in2out / panner_2in2out.cc
index 6bc0f93a8f369f46ad47dfe9f8e5e0e084b00397..e9d9ced0cab548a70e65963872a63872f4b141b3 100644 (file)
 
 #include <cmath>
 #include <cerrno>
-#include <fstream>
 #include <cstdlib>
 #include <string>
 #include <cstdio>
 #include <locale.h>
 #include <unistd.h>
 #include <float.h>
-#include <iomanip>
 
 #include <glibmm.h>
 
 #include "ardour/runtime_functions.h"
 #include "ardour/session.h"
 #include "ardour/utils.h"
+#include "ardour/mix.h"
 
 #include "panner_2in2out.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 
 #include "pbd/mathfix.h"
 
@@ -62,29 +61,39 @@ using namespace PBD;
 
 static PanPluginDescriptor _descriptor = {
         "Equal Power Stereo",
+        "http://ardour.org/plugin/panner_2in2out",
+        "http://ardour.org/plugin/panner_2in2out#ui",
         2, 2,
+        10000,
         Panner2in2out::factory
 };
 
-extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
+extern "C" ARDOURPANNER_API PanPluginDescriptor* panner_descriptor () { return &_descriptor; }
 
 Panner2in2out::Panner2in2out (boost::shared_ptr<Pannable> p)
        : Panner (p)
 {
-        _pannable->pan_azimuth_control->set_value (0.5);
-        _pannable->pan_width_control->set_value (1.0);
+        if (!_pannable->has_state()) {
+               _pannable->pan_azimuth_control->set_value (0.5, Controllable::NoGroup);
+               _pannable->pan_width_control->set_value (1.0, Controllable::NoGroup);
+        }
+
+        double const w = width();
+        double const wrange = min (position(), (1 - position())) * 2;
+        if (fabs(w) > wrange) {
+                set_width(w > 0 ? wrange : -wrange);
+        }
+
+
+        update ();
 
-        /* LEFT SIGNAL, panned hard left */
-        left[0] = 1.0;
-        right[0] = 0.0;
-        desired_left[0] = left_interp[0] = left[0];
-        desired_right[0] = right_interp[0] = right[0];
+        /* LEFT SIGNAL */
+        left_interp[0] = left[0] = desired_left[0];
+        right_interp[0] = right[0] = desired_right[0];
 
-        /* RIGHT SIGNAL, panned hard right */
-        left[1] = 0;
-        right[1] = 1.0;
-        desired_left[1] = left_interp[1] = left[1];
-        desired_right[1] = right_interp[1] = right[1];
+        /* RIGHT SIGNAL */
+        left_interp[1] = left[1] = desired_left[1];
+        right_interp[1] = right[1] = desired_right[1];
 
         _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
         _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
@@ -94,13 +103,13 @@ Panner2in2out::~Panner2in2out ()
 {
 }
 
-double 
+double
 Panner2in2out::position () const
 {
         return _pannable->pan_azimuth_control->get_value();
 }
 
-double 
+double
 Panner2in2out::width () const
 {
         return _pannable->pan_width_control->get_value();
@@ -110,7 +119,7 @@ void
 Panner2in2out::set_position (double p)
 {
         if (clamp_position (p)) {
-                _pannable->pan_azimuth_control->set_value (p);
+               _pannable->pan_azimuth_control->set_value (p, Controllable::NoGroup);
         }
 }
 
@@ -118,55 +127,72 @@ void
 Panner2in2out::set_width (double p)
 {
         if (clamp_width (p)) {
-                _pannable->pan_width_control->set_value (p);
+               _pannable->pan_width_control->set_value (p, Controllable::NoGroup);
         }
 }
 
+void
+Panner2in2out::thaw ()
+{
+       Panner::thaw ();
+       if (_frozen == 0) {
+               update ();
+       }
+}
+
 void
 Panner2in2out::update ()
 {
+       if (_frozen) {
+               return;
+       }
+
         /* it would be very nice to split this out into a virtual function
            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 = 180.0 degrees
            x == 1 => hard right = 0.0 degrees
         */
-        
+
         float pos[2];
-        const double width = _pannable->pan_width_control->get_value();
-        const double direction_as_lr_fract = _pannable->pan_azimuth_control->get_value();
+        double width = this->width ();
+        const double direction_as_lr_fract = position ();
 
-        cerr << "new pan values width=" << width << " LR = " << direction_as_lr_fract << endl;
+        double const wrange = min (position(), (1 - position())) * 2;
+        if (fabs(width) > wrange) {
+                width = (width  > 0 ? wrange : -wrange);
+        }
 
         if (width < 0.0) {
+                width = -width;
                 pos[0] = direction_as_lr_fract + (width/2.0); // left signal lr_fract
                 pos[1] = direction_as_lr_fract - (width/2.0); // right signal lr_fract
         } else {
                 pos[1] = direction_as_lr_fract + (width/2.0); // right signal lr_fract
                 pos[0] = direction_as_lr_fract - (width/2.0); // left signal lr_fract
         }
-        
+
         /* compute target gain coefficients for both input signals */
-        
+
         float const pan_law_attenuation = -3.0f;
         float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
         float panR;
         float panL;
-        
+
         /* left signal */
-        
+
         panR = pos[0];
         panL = 1 - panR;
         desired_left[0] = panL * (scale * panL + 1.0f - scale);
         desired_right[0] = panR * (scale * panR + 1.0f - scale);
-        
+
         /* right signal */
-        
+
         panR = pos[1];
         panL = 1 - panR;
         desired_left[1] = panL * (scale * panL + 1.0f - scale);
@@ -176,56 +202,64 @@ Panner2in2out::update ()
 bool
 Panner2in2out::clamp_position (double& p)
 {
-        double w = _pannable->pan_width_control->get_value();
+        double w = width ();
         return clamp_stereo_pan (p, w);
 }
 
 bool
 Panner2in2out::clamp_width (double& w)
 {
-        double p = _pannable->pan_azimuth_control->get_value();
+        double p = position ();
         return clamp_stereo_pan (p, w);
 }
 
+pair<double, double>
+Panner2in2out::position_range () const
+{
+       return make_pair (0.5 - (1 - width()) / 2, 0.5 + (1 - width()) / 2);
+}
+
+pair<double, double>
+Panner2in2out::width_range () const
+{
+       double const w = min (position(), (1 - position())) * 2;
+       return make_pair (-w, w);
+}
+
 bool
 Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
 {
-        double r_pos = direction_as_lr_fract + (width/2.0);
-        double l_pos = direction_as_lr_fract - (width/2.0);
-        bool can_move_left = true;
-        bool can_move_right = true;
+        double r_pos;
+        double l_pos;
 
-        cerr << "Clamp pos = " << direction_as_lr_fract << " w = " << width << endl;
+        width = max (min (width, 1.0), -1.0);
+        direction_as_lr_fract = max (min (direction_as_lr_fract, 1.0), 0.0);
 
-        if (width > 1.0 || width < 1.0) {
-                return false;
-        }
-
-        if (direction_as_lr_fract > 1.0 || direction_as_lr_fract < 0.0) {
-                return false;
-        }
+        r_pos = direction_as_lr_fract + (width/2.0);
+        l_pos = direction_as_lr_fract - (width/2.0);
 
         if (width < 0.0) {
                 swap (r_pos, l_pos);
         }
 
         /* if the new left position is less than or equal to zero (hard left) and the left panner
-           is already there, we're not moving the left signal. 
+           is already there, we're not moving the left signal.
         */
-        
-        if (l_pos <= 0.0 && desired_left[0] <= 0.0) {
-                can_move_left = false;
+
+        if (l_pos < 0.0) {
+                return false;
         }
 
         /* if the new right position is less than or equal to 1.0 (hard right) and the right panner
-           is already there, we're not moving the right signal. 
+           is already there, we're not moving the right signal.
         */
-        
-        if (r_pos >= 1.0 && desired_right[1] >= 1.0) {
-                can_move_right = false;
+
+        if (r_pos > 1.0) {
+                return false;
+
         }
 
-        return can_move_left && can_move_right;
+        return true;
 }
 
 void
@@ -238,7 +272,7 @@ Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gai
        pan_t pan;
 
        Sample* const src = srcbuf.data();
-        
+
        /* LEFT OUTPUT */
 
        dst = obufs.get_audio(0).data();
@@ -246,7 +280,7 @@ Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gai
        if (fabsf ((delta = (left[which] - desired_left[which]))) > 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 */
+                  interpolate over 64 samples or nframes, whichever is smaller */
 
                pframes_t const limit = min ((pframes_t) 64, nframes);
                pframes_t n;
@@ -276,6 +310,7 @@ Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gai
 
                                /* pan is 1 but also not 0, so we must do it "properly" */
 
+                               //obufs.get_audio(1).read_from (srcbuf, nframes);
                                mix_buffers_with_gain(dst,src,nframes,pan);
 
                                /* mark that we wrote into the buffer */
@@ -289,7 +324,7 @@ Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gai
                        /* pan is 1 so we can just copy the input samples straight in */
 
                        mix_buffers_no_gain(dst,src,nframes);
-                        
+
                        /* XXX it would be nice to mark that we wrote into the buffer */
                }
        }
@@ -301,7 +336,7 @@ Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gai
        if (fabsf ((delta = (right[which] - desired_right[which]))) > 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 */
+                  interpolate over 64 samples or nframes, whichever is smaller */
 
                pframes_t const limit = min ((pframes_t) 64, nframes);
                pframes_t n;
@@ -332,8 +367,9 @@ Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gai
                        if (pan != 0.0f) {
 
                                /* pan is not 1 but also not 0, so we must do it "properly" */
-                               
+
                                mix_buffers_with_gain(dst,src,nframes,pan);
+                               // obufs.get_audio(1).read_from (srcbuf, nframes);
 
                                /* XXX it would be nice to mark the buffer as written to */
                        }
@@ -341,7 +377,7 @@ Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gai
                } else {
 
                        /* pan is 1 so we can just copy the input samples straight in */
-                       
+
                        mix_buffers_no_gain(dst,src,nframes);
 
                        /* XXX it would be nice to mark the buffer as written to */
@@ -351,7 +387,7 @@ Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gai
 
 void
 Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
-                                         framepos_t start, framepos_t end, pframes_t nframes,
+                                         samplepos_t start, samplepos_t end, pframes_t nframes,
                                          pan_t** buffers, uint32_t which)
 {
        assert (obufs.count().n_audio() == 2);
@@ -387,7 +423,7 @@ Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
 
                 float panR;
 
-                if (which == 0) { 
+                if (which == 0) {
                         // panning left signal
                         panR = position[n] - (width[n]/2.0f); // center - width/2
                 } else {
@@ -395,15 +431,17 @@ Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
                         panR = position[n] + (width[n]/2.0f); // center - width/2
                 }
 
+                panR = max(0.f, min(1.f, panR));
+
                 const float panL = 1 - panR;
 
                 /* note that are overwriting buffers, but its OK
                    because we're finished with their old contents
                    (position/width automation data) and are
-                   replacing it with panning/gain coefficients 
+                   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);
         }
@@ -432,30 +470,78 @@ Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
 }
 
 Panner*
-Panner2in2out::factory (boost::shared_ptr<Pannable> p, Speakers& /* ignored */)
+Panner2in2out::factory (boost::shared_ptr<Pannable> p, boost::shared_ptr<Speakers> /* ignored */)
 {
        return new Panner2in2out (p);
 }
 
 XMLNode&
-Panner2in2out::get_state (void)
+Panner2in2out::get_state ()
 {
-       return state (true);
+       XMLNode& root (Panner::get_state ());
+       root.set_property (X_("uri"), _descriptor.panner_uri);
+       /* this is needed to allow new sessions to load with old Ardour: */
+       root.set_property (X_("type"), _descriptor.name);
+       return root;
 }
 
-XMLNode&
-Panner2in2out::state (bool /*full_state*/)
+std::set<Evoral::Parameter>
+Panner2in2out::what_can_be_automated() const
 {
-       XMLNode& root (Panner::get_state ());
-       root.add_property (X_("type"), _descriptor.name);
-       return root;
+        set<Evoral::Parameter> s;
+        s.insert (Evoral::Parameter (PanAzimuthAutomation));
+        s.insert (Evoral::Parameter (PanWidthAutomation));
+        return s;
+}
+
+string
+Panner2in2out::describe_parameter (Evoral::Parameter p)
+{
+        switch (p.type()) {
+        case PanAzimuthAutomation:
+                return _("L/R");
+        case PanWidthAutomation:
+                return _("Width");
+        default:
+                return _pannable->describe_parameter (p);
+        }
 }
 
-int
-Panner2in2out::set_state (const XMLNode& node, int version)
+string
+Panner2in2out::value_as_string (boost::shared_ptr<const AutomationControl> ac) const
 {
-       LocaleGuard lg (X_("POSIX"));
-       Panner::set_state (node, version);
-       return 0;
+        /* 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));
+
+        case PanWidthAutomation:
+                return string_compose (_("Width: %1%%"), (int) floor (100.0 * val));
+
+        default:
+                return _("unused");
+        }
 }
 
+void
+Panner2in2out::reset ()
+{
+       set_position (0.5);
+       set_width (1);
+       update ();
+}