globally change all use of "frame" to refer to audio into "sample".
[ardour.git] / libs / panners / 2in2out / panner_2in2out.cc
index f5ed858da285e2cbe5a36ae69f528dc5abb2c33c..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,30 +61,40 @@ 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)
 {
         if (!_pannable->has_state()) {
-                _pannable->pan_azimuth_control->set_value (0.5);
-                _pannable->pan_width_control->set_value (1.0);
-        } 
-        
+               _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 */
         left_interp[0] = left[0] = desired_left[0];
-        right_interp[0] = right[0] = desired_right[0]; 
-        
+        right_interp[0] = right[0] = desired_right[0];
+
         /* 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,54 +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];
-        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 ();
+
+        double const wrange = min (position(), (1 - position())) * 2;
+        if (fabs(width) > wrange) {
+                width = (width  > 0 ? wrange : -wrange);
+        }
 
         if (width < 0.0) {
-                width = fabs (width);
+                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);
@@ -175,17 +202,30 @@ 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)
 {
@@ -203,20 +243,20 @@ Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
         }
 
         /* 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) {
                 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) {
                 return false;
-                
+
         }
 
         return true;
@@ -232,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();
@@ -240,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;
@@ -270,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 */
@@ -283,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 */
                }
        }
@@ -295,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;
@@ -326,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 */
                        }
@@ -335,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 */
@@ -345,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);
@@ -381,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 {
@@ -389,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);
         }
@@ -426,34 +470,22 @@ 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)
-{
-       return state (true);
-}
-
-XMLNode&
-Panner2in2out::state (bool /*full_state*/)
+Panner2in2out::get_state ()
 {
        XMLNode& root (Panner::get_state ());
-       root.add_property (X_("type"), _descriptor.name);
+       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;
 }
 
-int
-Panner2in2out::set_state (const XMLNode& node, int version)
-{
-       LocaleGuard lg (X_("POSIX"));
-       Panner::set_state (node, version);
-       return 0;
-}
-
-std::set<Evoral::Parameter> 
+std::set<Evoral::Parameter>
 Panner2in2out::what_can_be_automated() const
 {
         set<Evoral::Parameter> s;
@@ -475,8 +507,8 @@ Panner2in2out::describe_parameter (Evoral::Parameter p)
         }
 }
 
-string 
-Panner2in2out::value_as_string (boost::shared_ptr<AutomationControl> ac) const
+string
+Panner2in2out::value_as_string (boost::shared_ptr<const AutomationControl> ac) const
 {
         /* DO NOT USE LocaleGuard HERE */
         double val = ac->get_value();
@@ -484,20 +516,32 @@ Panner2in2out::value_as_string (boost::shared_ptr<AutomationControl> ac) const
         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) 
+                   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:%1 R:%2"), (int) rint (100.0 * (1.0 - val)),
+
+                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 _pannable->value_as_string (ac);
+                return _("unused");
         }
 }
+
+void
+Panner2in2out::reset ()
+{
+       set_position (0.5);
+       set_width (1);
+       update ();
+}