one step closer to working vbap panning
authorPaul Davis <paul@linuxaudiosystems.com>
Fri, 26 Nov 2010 17:43:03 +0000 (17:43 +0000)
committerPaul Davis <paul@linuxaudiosystems.com>
Fri, 26 Nov 2010 17:43:03 +0000 (17:43 +0000)
git-svn-id: svn://localhost/ardour2/branches/3.0@8091 d708f5d6-7413-0410-9779-e7cbd77b26cf

18 files changed:
gtk2_ardour/panner2d.cc
gtk2_ardour/panner2d.h
gtk2_ardour/panner_ui.cc
libs/ardour/ardour/panner.h
libs/ardour/ardour/session.h
libs/ardour/ardour/speaker.h [new file with mode: 0644]
libs/ardour/ardour/speakers.h [new file with mode: 0644]
libs/ardour/ardour/vbap.h
libs/ardour/ardour/vbap_speakers.h
libs/ardour/panner.cc
libs/ardour/session.cc
libs/ardour/speakers.cc [new file with mode: 0644]
libs/ardour/vbap.cc
libs/ardour/vbap_speakers.cc
libs/ardour/wscript
libs/pbd/cartesian.cc
libs/pbd/pbd/cartesian.h
libs/surfaces/mackie/mackie_control_protocol.cc

index 39501122b2e10f63fb624f036b6cfa522b77691d..2864bfe607810901815e487b62d66fe2a1384510 100644 (file)
@@ -41,14 +41,11 @@ using namespace ARDOUR;
 using namespace PBD;
 using Gtkmm2ext::Keyboard;
 
-Panner2d::Target::Target (float xa, float ya, const char *txt)
-       : x (xa, 0.0, 1.0, 0.01, 0.1)
-       , y (ya, 0.0, 1.0, 0.01, 0.1)
-       , azimuth (M_PI/2.0, 0.0, 2.0 * M_PI, 0.1, 0.5)
+Panner2d::Target::Target (const AngularVector& a, const char *txt)
+       : position (a)
        , text (txt)
         , _selected (false)
 {
-       azimuth.set_value ((random() / (double) INT_MAX) * (2.0 * M_PI));
 }
 
 Panner2d::Target::~Target ()
@@ -64,10 +61,6 @@ Panner2d::Target::set_text (const char* txt)
 Panner2d::Panner2d (boost::shared_ptr<Panner> p, int32_t h)
        : panner (p), width (0), height (h)
 {
-       allow_x = false;
-       allow_y = false;
-       allow_target = false;
-
        panner->StateChanged.connect (state_connection, invalidator (*this), boost::bind (&Panner2d::handle_state_change, this), gui_context());
        panner->Changed.connect (change_connection, invalidator (*this), boost::bind (&Panner2d::handle_position_change, this), gui_context());
 
@@ -90,7 +83,7 @@ Panner2d::reset (uint32_t n_inputs)
        /* pucks */
 
        while (pucks.size() < n_inputs) {
-               add_puck ("", 0.0, 0.0);
+               add_puck ("", AngularVector());
        }
 
        if (pucks.size() > n_inputs) {
@@ -128,25 +121,14 @@ Panner2d::reset (uint32_t n_inputs)
        }
 
        for (uint32_t i = existing_pucks; i < n_inputs; ++i) {
-               float x, y;
-                double dx, dy;
-
-               panner->streampanner (i).get_position (x, y);
-
-                dx = x;
-                dy = y;
-                clamp_to_circle (dx, dy);
-
-               pucks[i]->x.set_value (dx);
-               pucks[i]->y.set_value (dy);
-
+               pucks[i]->position = panner->streampanner (i).get_position ();
                pucks[i]->visible = true;
        }
 
        /* add all outputs */
 
        while (targets.size() < panner->nouts()) {
-               add_target (0.0, 0.0);
+               add_target (AngularVector());
        }
 
        if (targets.size() > panner->nouts()) {
@@ -166,25 +148,13 @@ Panner2d::reset (uint32_t n_inputs)
 
                snprintf (buf, sizeof (buf), "%d", n+1);
                targets[n]->set_text (buf);
-               targets[n]->x.set_value (panner->output(n).x);
-               targets[n]->y.set_value (panner->output(n).y);
+               targets[n]->position = panner->output(n).position;
                targets[n]->visible = true;
        }
 
-       allow_x_motion (true);
-       allow_y_motion (true);
-       allow_target_motion (true);
-
        queue_draw ();
 }
 
-Gtk::Adjustment&
-Panner2d::azimuth (uint32_t which)
-{
-       assert (which < pucks.size());
-       return pucks[which]->azimuth;
-}
-
 void
 Panner2d::on_size_allocate (Gtk::Allocation& alloc)
 {
@@ -200,16 +170,9 @@ Panner2d::on_size_allocate (Gtk::Allocation& alloc)
 }
 
 int
-Panner2d::add_puck (const char* text, float x, float y)
+Panner2d::add_puck (const char* text, const AngularVector& a)
 {
-        double dx, dy;
-        
-        dx = x;
-        dy = y;
-        
-        clamp_to_circle (dx, dy);
-
-       Target* puck = new Target (dx, dy, text);
+       Target* puck = new Target (a, text);
        pucks.push_back (puck);
        puck->visible = true;
 
@@ -217,9 +180,9 @@ Panner2d::add_puck (const char* text, float x, float y)
 }
 
 int
-Panner2d::add_target (float x, float y)
+Panner2d::add_target (const AngularVector& a)
 {
-       Target* target = new Target (x, y, "");
+       Target* target = new Target (a, "");
        targets.push_back (target);
        target->visible = true;
        queue_draw ();
@@ -242,57 +205,53 @@ Panner2d::handle_position_change ()
        ENSURE_GUI_THREAD (*this, &Panner2d::handle_position_change)
 
        for (n = 0; n < pucks.size(); ++n) {
-               float x, y;
-               panner->streampanner(n).get_position (x, y);
-               pucks[n]->x.set_value (x);
-               pucks[n]->y.set_value (y);
+               pucks[n]->position = panner->streampanner(n).get_position ();
        }
 
        for (n = 0; n < targets.size(); ++n) {
-               targets[n]->x.set_value (panner->output(n).x);
-               targets[n]->y.set_value (panner->output(n).y);
+                targets[n]->position = panner->output(n).position;
        }
 
        queue_draw ();
 }
 
 void
-Panner2d::move_puck (int which, float x, float y)
+Panner2d::move_puck (int which, const AngularVector& a)
 {
        if (which >= int (targets.size())) {
                return;
        }
        
-       targets[which]->x.set_value (x);
-       targets[which]->y.set_value (y);
+       targets[which]->position = a;
        queue_draw ();
 }
 
 Panner2d::Target *
 Panner2d::find_closest_object (gdouble x, gdouble y, int& which) const
 {
-       gdouble efx, efy;
-       gdouble cx, cy;
        Target *closest = 0;
        Target *candidate;
        float distance;
        float best_distance = FLT_MAX;
        int pwhich;
 
-       efx = x/(width-1.0);
-        efy = 1.0 - (y/(height-1.0)); /* convert from X Window origin */
-
        which = 0;
        pwhich = 0;
 
+        cerr << "@ " << x << ", " << y << endl;
+
        for (Targets::const_iterator i = pucks.begin(); i != pucks.end(); ++i, ++pwhich) {
                candidate = *i;
 
-               cx = candidate->x.get_value();
-               cy = candidate->y.get_value();
+                CartesianVector c;
+
+                candidate->position.cartesian (c);
+                cart_to_gtk (c);
+
+               distance = sqrt ((c.x - x) * (c.x - x) +
+                                (c.y - y) * (c.y - y));
 
-               distance = sqrt ((cx - efx) * (cx - efx) +
-                                (cy - efy) * (cy - efy));
+                cerr << "\tConsider candiate " << candidate->text << " @ " << c.x << ", " << c.y << ", " << c.z <<  " distance = " << distance << endl;
 
                if (distance < best_distance) {
                        closest = candidate;
@@ -301,10 +260,13 @@ Panner2d::find_closest_object (gdouble x, gdouble y, int& which) const
                }
        }
 
-        if (best_distance > 0.05) { // arbitrary 
+
+        if (best_distance > 20) { // arbitrary 
                 return 0;
         }
 
+        cerr << "the winner is " << closest->text << endl;
+
        return closest;
 }
 
@@ -328,7 +290,6 @@ bool
 Panner2d::on_expose_event (GdkEventExpose *event)
 {
        gint x, y;
-       float fx, fy;
        cairo_t* cr;
 
        cr = gdk_cairo_create (get_window()->gobj());
@@ -386,25 +347,26 @@ Panner2d::on_expose_event (GdkEventExpose *event)
                        if (puck->visible) {
                                /* redraw puck */
 
-                               fx = min (puck->x.get_value(), 1.0);
-                               fx = max (fx, -1.0f);
-                               x = (gint) floor (width * fx - 4);
-
-                               fy = min (fy, 1.0f);
-                               fy = max (fy, -1.0f);
-
-                                /* translate back to X Window abomination coordinates */
-                                fy = -(puck->y.get_value() - 1.0);
-
-                               y = (gint) floor (height * fy - 4);
+                                CartesianVector c;
+                                
+                                puck->position.cartesian (c);
+                                cart_to_gtk (c);
                                 
+                                x = (gint) floor (c.x);
+                                y = (gint) floor (c.y);
+
+                                /* XXX need to shift circles so that they are centered on the circle */
+                                        
                                cairo_arc (cr, x, y, arc_radius, 0, 2.0 * M_PI);
                                cairo_set_source_rgb (cr, 0.8, 0.2, 0.1);
                                cairo_close_path (cr);
                                cairo_fill (cr);
 
                                cairo_move_to (cr, x + 6, y + 6);
-                               cairo_show_text (cr, puck->text.c_str());
+
+                                char buf[256];
+                                snprintf (buf, sizeof (buf), "%s:%d", puck->text.c_str(), (int) lrint (puck->position.azi));
+                               cairo_show_text (cr, buf);
                        }
                }
 
@@ -419,14 +381,14 @@ Panner2d::on_expose_event (GdkEventExpose *event)
 
                        if (target->visible) {
 
-                               fx = min (target->x.get_value(), 1.0);
-                               fx = max (fx, -1.0f);
-                               x = (gint) floor (width  * fx);
-
-                               fy = min (target->y.get_value(), 1.0);
-                               fy = max (fy, -1.0f);
-                               y = (gint) floor (height * fy);
+                                CartesianVector c;
+                                
+                                target->position.cartesian (c);
+                                cart_to_gtk (c);
 
+                                x = (int) floor (c.x);
+                                y = (int) floor (c.y);
+                                
                                snprintf (buf, sizeof (buf), "%d", n);
 
                                cairo_set_source_rgb (cr, 0.0, 0.8, 0.1);
@@ -434,6 +396,7 @@ Panner2d::on_expose_event (GdkEventExpose *event)
                                cairo_fill (cr);
                                cairo_move_to (cr, x+6, y+6);
                                cairo_show_text (cr, buf);
+
                        }
                }
        }
@@ -541,88 +504,80 @@ Panner2d::handle_motion (gint evx, gint evy, GdkModifierType state)
 
        if (state & GDK_BUTTON1_MASK && !(state & GDK_BUTTON2_MASK)) {
 
-                double fx = evx;
-                double fy = evy;
+                CartesianVector c;
                 bool need_move = false;
+                
+                drag_target->position.cartesian (c);
+                cart_to_gtk (c);
 
-                clamp_to_circle (fx, fy);
-
-                if ((fx != drag_target->x.get_value()) || (fy != drag_target->y.get_value())) {
+                if ((evx != c.x) || (evy != c.y)) {
                         need_move = true;
                 }
 
                 if (need_move) {
-                        drag_target->x.set_value (fx);
-                        drag_target->y.set_value (fy);
-                        
-                        panner->streampanner (drag_index).set_position (drag_target->x.get_value(), drag_target->y.get_value(), false);
-                       queue_draw ();
-               }
+                        CartesianVector cp (evx, evy, 0.0);
 
-       } else if ((state & GDK_BUTTON2_MASK) && !(state & GDK_BUTTON1_MASK)) {
+                        /* canonicalize position */
 
-               int xdelta = drag_x - evx;
-               int ydelta = drag_x - evy;
+                        gtk_to_cart (cp);
 
-               drag_target->azimuth.set_value (drag_target->azimuth.get_value() + (2 * M_PI) * ((float)ydelta)/height * ((float) -xdelta)/height);
-               queue_draw ();
-       }
+                        /* position actual signal on circle */
+
+                        clamp_to_circle (cp.x, cp.y);
+                        
+                        /* generate an angular representation and set drag target (GUI) position */
+
+                        cp.angular (drag_target->position); /* sets drag target position */
+
+                        panner->streampanner (drag_index).set_position (drag_target->position);
+                        
+                       queue_draw ();
+               }
+       } 
 
        return true;
 }
 
 void
-Panner2d::cart_to_azi_ele (double x, double y, double& azi, double& ele)
+Panner2d::cart_to_gtk (CartesianVector& c) const
 {
-        x = min (x, (double) width);
-        x = max (x, 0.0);
-        x = x / (width-1.0);
-
-        y = min (y, (double) height);
-        y = max (y, 0.0);
-        y = y / (height-1.0);
-
-        /* at this point, new_x and new_y are in the range [ 0.0 .. 1.0 ], with
-           (0,0) at the upper left corner (thank you, X Window)
-           
-           we need to translate to (0,0) at center
+        /* "c" uses a coordinate space that is:
+            
+               center = 0.0
+               dimension = 2.0 * 2.0
+               so max values along each axis are -1..+1
+
+           GTK uses a coordinate space that is:
+
+               top left = 0.0
+               dimension = width * height
+               so max values along each axis are 0,width and
+                                                 0,height
         */
-        
-        x -= 0.5;
-        y  = (1.0 - y) - 0.5;
 
-        PBD::cart_to_azi_ele (x, y, 0.0, azi, ele);
+        c.x = (width / 2) * (c.x + 1);
+        c.y = (height / 2) * (1 - c.y);
+
+        /* XXX z-axis not handled - 2D for now */
 }
 
 void
-Panner2d::azi_ele_to_cart (double azi, double ele, double& x, double& y)
+Panner2d::gtk_to_cart (CartesianVector& c) const
 {
-        double z;
+        c.x = (c.x / (width / 2.0)) - 1.0;
+        c.y = -((c.y / (height / 2.0)) - 1.0);
 
-        PBD::azi_ele_to_cart (azi, ele, x, y, z);
-        
-        /* xp,yp,zp use a (0,0) == center and 2.0 unit dimension. so convert
-           back to (0,0) and 1.0 unit dimension
-        */
-        
-        x /= 2.0;
-        y /= 2.0;
-        z /= 2.0;
-        
-        /* and now convert back to (0,0) == upper left corner */
-        
-        x += 0.5;
-        y += 0.5;
-        z += 0.5;
+        /* XXX z-axis not handled - 2D for now */
 }
 
 void
 Panner2d::clamp_to_circle (double& x, double& y)
 {
         double azi, ele;
-
-        cart_to_azi_ele (x, y, azi, ele);
-        azi_ele_to_cart (azi, ele, x, y);
+        double z = 0.0;
+        
+        PBD::cart_to_azi_ele (x, y, z, azi, ele);
+        PBD::azi_ele_to_cart (azi, ele, x, y, z);
 }
 
 void
@@ -631,24 +586,6 @@ Panner2d::toggle_bypass ()
        panner->set_bypassed (!panner->bypassed());
 }
 
-void
-Panner2d::allow_x_motion (bool yn)
-{
-       allow_x = yn;
-}
-
-void
-Panner2d::allow_target_motion (bool yn)
-{
-       allow_target = yn;
-}
-
-void
-Panner2d::allow_y_motion (bool yn)
-{
-       allow_y = yn;
-}
-
 Panner2dWindow::Panner2dWindow (boost::shared_ptr<Panner> p, int32_t h, uint32_t inputs)
        : widget (p, h)
        , reset_button (_("Reset"))
@@ -694,10 +631,11 @@ Panner2dWindow::reset (uint32_t n_inputs)
 {
        widget.reset (n_inputs);
 
+#if 0
        while (spinners.size() < n_inputs) {
-               spinners.push_back (new Gtk::SpinButton (widget.azimuth (spinners.size())));
-               spinner_box.pack_start (*spinners.back(), false, false);
-               spinners.back()->set_digits (4);
+               // spinners.push_back (new Gtk::SpinButton (widget.azimuth (spinners.size())));
+               //spinner_box.pack_start (*spinners.back(), false, false);
+               //spinners.back()->set_digits (4);
                spinners.back()->show ();
        }
 
@@ -706,4 +644,5 @@ Panner2dWindow::reset (uint32_t n_inputs)
                delete spinners.back();
                spinners.erase (--spinners.end());
        }
+#endif
 }
index fe8305343e7b4e9d227ea1b7175b44793803f0e5..e1572c11b25a271b8a6dda8d353e29099b593cf0 100644 (file)
@@ -32,6 +32,8 @@
 #include <gtkmm/spinbutton.h>
 #include <gtkmm/adjustment.h>
 
+#include "pbd/cartesian.h"
+
 namespace ARDOUR {
        class Panner;
 }
@@ -53,22 +55,21 @@ class Panner2d : public Gtk::DrawingArea
        Panner2d (boost::shared_ptr<ARDOUR::Panner>, int32_t height);
        ~Panner2d ();
 
-       void allow_x_motion(bool);
-       void allow_y_motion(bool);
        void allow_target_motion (bool);
 
-       int  add_target (float x, float y);
-       int  add_puck (const char* text, float x, float y);
-       void move_puck (int, float x, float y);
+       int  add_target (const PBD::AngularVector&);
+       int  add_puck (const char* text, const PBD::AngularVector&);
+       void move_puck (int which, const PBD::AngularVector&);
        void reset (uint32_t n_inputs);
 
-       Gtk::Adjustment& azimuth (uint32_t which);
-
        boost::shared_ptr<ARDOUR::Panner> get_panner() const { return panner; }
 
        sigc::signal<void,int> PuckMoved;
        sigc::signal<void,int> TargetMoved;
 
+        void cart_to_gtk (PBD::CartesianVector&) const;
+        void gtk_to_cart (PBD::CartesianVector&) const;
+
   protected:
        bool on_expose_event (GdkEventExpose *);
        bool on_button_press_event (GdkEventButton *);
@@ -79,25 +80,23 @@ class Panner2d : public Gtk::DrawingArea
   private:
        class Target {
           public:
-           Gtk::Adjustment x;
-           Gtk::Adjustment y;
-           Gtk::Adjustment azimuth;
-           bool visible;
-            std::string text;
-
-           Target (float xa, float ya, const char* txt = 0);
-           ~Target ();
-
-           void set_text (const char*);
-            void set_selected (bool yn) {
-                    _selected = yn;
-            }
-            bool selected() const { 
-                    return _selected;
-            }
-
+                PBD::AngularVector position;
+                bool visible;
+                std::string text;
+                
+                Target (const PBD::AngularVector&, const char* txt = 0);
+                ~Target ();
+                
+                void set_text (const char*);
+                void set_selected (bool yn) {
+                        _selected = yn;
+                }
+                bool selected() const { 
+                        return _selected;
+                }
+                
           private:
-            bool _selected;
+                bool _selected;
        };
 
        boost::shared_ptr<ARDOUR::Panner> panner;
@@ -108,14 +107,12 @@ class Panner2d : public Gtk::DrawingArea
        Targets pucks;
 
        Target *drag_target;
-       int drag_x;
-       int drag_y;
+       int     drag_x;
+       int     drag_y;
        int     drag_index;
-       bool  allow_x;
-       bool  allow_y;
-       bool  allow_target;
-       int width;
-       int height;
+       bool    allow_target;
+       int     width;
+       int     height;
 
        bool bypassflag;
 
@@ -133,12 +130,6 @@ class Panner2d : public Gtk::DrawingArea
        PBD::ScopedConnection state_connection;
        PBD::ScopedConnection change_connection;
 
-        /* cartesian coordinates in GTK units ; return azimuth & elevation in degrees */
-        void cart_to_azi_ele (double x, double y, double& azi, double& eli);
-
-        /* azimuth & elevation in degrees; return cartesian coordinates in GTK units */
-        void azi_ele_to_cart (double azi, double eli, double& x, double& y);
-
         /* cartesian coordinates in GTK units ; adjust to same but on a circle of radius 1.0
            and centered in the middle of our area
         */
index 25580f0f511c032d81ff871d039dbd6a64cb334b..358e99c88b1927039c64dc0efc386bc335ddd40e 100644 (file)
@@ -666,23 +666,18 @@ PannerUI::pan_value_changed (uint32_t which)
 
        if (twod_panner) {
 
-               float x;
-               float y;
-               _panner->streampanner(which).get_position (x, y);
-
                in_pan_update = true;
-               twod_panner->move_puck (which, x, y);
+               twod_panner->move_puck (which, _panner->streampanner(which).get_position());
                in_pan_update = false;
 
        } else if (_panner->npanners() > 0 && which < _panner->npanners()) {
-               float xpos;
-               float val = pan_adjustments[which]->get_value ();
-
-               _panner->streampanner(which).get_position (xpos);
+                AngularVector model = _panner->streampanner(which).get_position();
+                double fract = pan_adjustments[which]->get_value();
+                AngularVector view (BaseStereoPanner::lr_fract_to_azimuth (fract), 0.0);
 
-               if (!Panner::equivalent (val, xpos)) {
+               if (!Panner::equivalent (model, view)) {
                        in_pan_update = true;
-                       pan_adjustments[which]->set_value (xpos);
+                       pan_adjustments[which]->set_value (BaseStereoPanner::azimuth_to_lr_fract (model.azi));
                        in_pan_update = false;
                }
        }
@@ -701,7 +696,6 @@ PannerUI::update_pan_bars (bool only_if_aplay)
        */
 
        for (i = pan_adjustments.begin(), n = 0; i != pan_adjustments.end(); ++i, ++n) {
-               float xpos, val;
 
                if (only_if_aplay) {
                        boost::shared_ptr<AutomationList> alist (_panner->streampanner(n).pan_control()->alist());
@@ -711,11 +705,12 @@ PannerUI::update_pan_bars (bool only_if_aplay)
                        }
                }
 
-               _panner->streampanner(n).get_effective_position (xpos);
-               val = (*i)->get_value ();
+                AngularVector model = _panner->streampanner(n).get_effective_position();
+                double fract = (*i)->get_value();
+                AngularVector view (BaseStereoPanner::lr_fract_to_azimuth (fract), 0.0);
 
-               if (!Panner::equivalent (val, xpos)) {
-                       (*i)->set_value (xpos);
+               if (!Panner::equivalent (model, view)) {
+                       (*i)->set_value (BaseStereoPanner::azimuth_to_lr_fract (model.azi));
                }
        }
 
index da68ed8578a8836b371a3e4eabfc85f20c44354e..a4e063884ec4b62e363d6edd77fab36070be6f66 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "pbd/stateful.h"
 #include "pbd/controllable.h"
+#include "pbd/cartesian.h"
 
 #include "ardour/types.h"
 #include "ardour/automation_control.h"
@@ -39,6 +40,7 @@ class Session;
 class Panner;
 class BufferSet;
 class AudioBuffer;
+class Speakers;
 
 class StreamPanner : public PBD::Stateful
 {
@@ -49,17 +51,10 @@ class StreamPanner : public PBD::Stateful
        void set_muted (bool yn);
        bool muted() const { return _muted; }
 
-       void set_position (float x, bool link_call = false);
-       void set_position (float x, float y, bool link_call = false);
-       void set_position (float x, float y, float z, bool link_call = false);
-
-       void get_position (float& xpos) const { xpos = _x; }
-       void get_position (float& xpos, float& ypos) const { xpos = _x; ypos = _y; }
-       void get_position (float& xpos, float& ypos, float& zpos) const { xpos = _x; ypos = _y; zpos = _z; }
-
-       void get_effective_position (float& xpos) const { xpos = effective_x; }
-       void get_effective_position (float& xpos, float& ypos) const { xpos = effective_x; ypos = effective_y; }
-       void get_effective_position (float& xpos, float& ypos, float& zpos) const { xpos = effective_x; ypos = effective_y; zpos = effective_z; }
+        const PBD::AngularVector& get_position() const { return _angles; }
+        const PBD::AngularVector& get_effective_position() const { return _effective_angles; }
+        void set_position (const PBD::AngularVector&, bool link_call = false);
+        void set_diffusion (double);
 
        void distribute (AudioBuffer &, BufferSet &, gain_t, nframes_t);
        void distribute_automated (AudioBuffer &, BufferSet &, nframes_t, nframes_t, nframes_t, pan_t **);
@@ -80,7 +75,7 @@ class StreamPanner : public PBD::Stateful
 
        boost::shared_ptr<AutomationControl> pan_control()  { return _control; }
 
-       PBD::Signal0<void> Changed;      /* for position */
+       PBD::Signal0<void> Changed;      /* for position or diffusion */
        PBD::Signal0<void> StateChanged; /* for mute, mono */
 
        int set_state (const XMLNode&, int version);
@@ -97,17 +92,9 @@ class StreamPanner : public PBD::Stateful
 
        void set_mono (bool);
        
-       float _x;
-       float _y;
-       float _z;
-
-       /* these are for automation. they store the last value
-          used by the most recent process() cycle.
-       */
-
-       float effective_x;
-       float effective_y;
-       float effective_z;
+        PBD::AngularVector _angles;
+        PBD::AngularVector _effective_angles;
+        double        _diffusion; 
 
        bool _muted;
        bool _mono;
@@ -116,7 +103,7 @@ class StreamPanner : public PBD::Stateful
 
        void add_state (XMLNode&);
 
-       /* Update internal parameters based on _x, _y and _z */
+       /* Update internal parameters based on this.angles */
        virtual void update () = 0;
 };
 
@@ -134,6 +121,19 @@ class BaseStereoPanner : public StreamPanner
 
        void do_distribute (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, nframes_t nframes);
 
+        static double azimuth_to_lr_fract (double azi) { 
+                /* 180.0 degrees=> left => 0.0 */
+                /* 0.0 degrees => right => 1.0 */
+                return 1.0 - (azi/180.0);
+        }
+
+        static double lr_fract_to_azimuth (double fract) { 
+                /* fract = 0.0 => degrees = 180.0 => left */
+                /* fract = 1.0 => degrees = 0.0 => right */
+                return 180.0 - (fract * 180.0);
+        }
+                
+
        /* old school automation loading */
 
        int load (std::istream&, std::string path, uint32_t&);
@@ -159,7 +159,7 @@ class EqualPowerStereoPanner : public BaseStereoPanner
        void get_current_coefficients (pan_t*) const;
        void get_desired_coefficients (pan_t*) const;
 
-       static StreamPanner* factory (Panner&, Evoral::Parameter param);
+       static StreamPanner* factory (Panner&, Evoral::Parameter param, Speakers&);
        static std::string name;
 
        XMLNode& state (bool full_state); 
@@ -170,32 +170,6 @@ class EqualPowerStereoPanner : public BaseStereoPanner
        void update ();
 };
 
-class Multi2dPanner : public StreamPanner
-{
-  public:
-       Multi2dPanner (Panner& parent, Evoral::Parameter);
-       ~Multi2dPanner ();
-
-       void do_distribute (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, nframes_t nframes);
-       void do_distribute_automated (AudioBuffer& src, BufferSet& obufs,
-                                     nframes_t start, nframes_t end, nframes_t nframes, pan_t** buffers);
-
-       static StreamPanner* factory (Panner&, Evoral::Parameter);
-       static std::string name;
-
-       XMLNode& state (bool full_state);
-       XMLNode& get_state (void);
-       int set_state (const XMLNode&, int version);
-
-       /* old school automation loading */
-
-       int load (std::istream&, std::string path, uint32_t&);
-
-  private:
-       void update ();
-};
-
-
 /** Class to pan from some number of inputs to some number of outputs.
  *  This class has a number of StreamPanners, one for each input.
  */
@@ -203,14 +177,12 @@ class Panner : public SessionObject, public Automatable
 {
 public:
        struct Output {
-            float x;
-            float y;
-            float z;
+            PBD::AngularVector position;
             pan_t current_pan;
             pan_t desired_pan;
             
-            Output (float xp, float yp, float zp = 0.0)
-            : x (xp), y (yp), z (zp), current_pan (0), desired_pan (0) {}
+            Output (const PBD::AngularVector& a) 
+            : position (a), current_pan (0), desired_pan (0) {}
 
        };
 
@@ -249,6 +221,10 @@ public:
        static bool equivalent (pan_t a, pan_t b) {
                return fabsf (a - b) < 0.002; // about 1 degree of arc for a stereo panner
        }
+       static bool equivalent (const PBD::AngularVector& a, const PBD::AngularVector& b) {
+                /* XXX azimuth only, at present */
+               return fabs (a.azi - b.azi) < 1.0;
+       }
 
        void move_output (uint32_t, float x, float y);
        uint32_t nouts() const { return outputs.size(); }
@@ -274,9 +250,7 @@ public:
 
        /* only StreamPanner should call these */
 
-       void set_position (float x, StreamPanner& orig);
-       void set_position (float x, float y, StreamPanner& orig);
-       void set_position (float x, float y, float z, StreamPanner& orig);
+       void set_position (const PBD::AngularVector&, StreamPanner& orig);
 
        /* old school automation */
 
index ff69a9ebdf396c9a3aca0df8b3c1e5a3521369f1..db334f6f19c7ba5a41aea4b9458681c70d25149f 100644 (file)
@@ -52,7 +52,7 @@
 #include "ardour/location.h"
 #include "ardour/timecode.h"
 #include "ardour/interpolation.h"
-#include "ardour/vbap_speakers.h"
+#include "ardour/speakers.h"
 
 #ifdef HAVE_JACK_SESSION
 #include <jack/session.h>
@@ -118,6 +118,7 @@ class SessionMetadata;
 class SessionPlaylists;
 class Slave;
 class Source;
+class Speakers;
 class TempoMap;
 class VSTPlugin;
 class Graph;
@@ -728,7 +729,7 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi
 
        /* Speakers */
 
-       VBAPSpeakers& get_speakers ();
+        Speakers& get_speakers ();
 
        /* Controllables */
 
@@ -1470,7 +1471,7 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi
        void start_time_changed (framepos_t);
        void end_time_changed (framepos_t);
 
-       VBAPSpeakers* _speakers; 
+        Speakers* _speakers; 
 };
 
 } // namespace ARDOUR
diff --git a/libs/ardour/ardour/speaker.h b/libs/ardour/ardour/speaker.h
new file mode 100644 (file)
index 0000000..8e9e911
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+    Copyright (C) 2010 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef __libardour_speaker_h__
+#define __libardour_speaker_h__
+
+#include "pbd/cartesian.h"
+
+namespace ARDOUR { 
+
+class Speaker {
+  public:
+        Speaker (int, const PBD::AngularVector& position);
+        
+        void move (const PBD::AngularVector& new_position);
+
+        const PBD::CartesianVector& coords() const { return _coords; }
+        const PBD::AngularVector&   angles() const { return _angles; }
+
+        int id;
+
+  private:
+        PBD::CartesianVector _coords;
+        PBD::AngularVector   _angles;
+};
+
+} /* namespace */
+
+#endif /* __libardour_speaker_h__ */
diff --git a/libs/ardour/ardour/speakers.h b/libs/ardour/ardour/speakers.h
new file mode 100644 (file)
index 0000000..ee8d752
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+    Copyright (C) 2010 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef __libardour_speakers_h__
+#define __libardour_speakers_h__
+
+#include <vector>
+#include <iostream>
+
+#include <pbd/signals.h>
+
+#include "ardour/speaker.h"
+
+namespace ARDOUR  {
+
+class Speakers {
+  public:
+        Speakers ();
+        virtual ~Speakers ();
+
+        virtual int  add_speaker (const PBD::AngularVector&);
+        virtual void remove_speaker (int id);
+        virtual void move_speaker (int id, const PBD::AngularVector& new_position);
+        virtual void clear_speakers ();
+
+        std::vector<Speaker>& speakers() { return _speakers; }
+
+        void dump_speakers (std::ostream&);
+
+        PBD::Signal0<void> Changed;
+
+  protected:
+        std::vector<Speaker>  _speakers;
+
+        virtual void update () {}  
+};
+
+} /* namespace */
+
+#endif /* __libardour_speakers_h__ */
index b6c2b7a710f86d428740006a7d0cec2f07c580dd..620bd9d29770492996b4cdcc75069ad8aee32220 100644 (file)
 #include <string>
 #include <map>
 
-#include <pbd/signals.h>
-
 #include "ardour/panner.h"
+#include "ardour/vbap_speakers.h"
 
 namespace ARDOUR {
 
-class VBAPSpeakers;
+class Speakers;
 
 class VBAPanner : public StreamPanner { 
   public:
-        VBAPanner (Panner& parent, Evoral::Parameter param, VBAPSpeakers& s);
+        VBAPanner (Panner& parent, Evoral::Parameter param, Speakers& s);
         ~VBAPanner ();
 
+        static StreamPanner* factory (Panner& parent, Evoral::Parameter param, Speakers& s);
+        static std::string name;
+
         void do_distribute (AudioBuffer&, BufferSet& obufs, gain_t gain_coeff, nframes_t nframes);
        void do_distribute_automated (AudioBuffer& src, BufferSet& obufs,
                                       nframes_t start, nframes_t end, nframes_t nframes, pan_t** buffers);
@@ -58,8 +60,6 @@ class VBAPanner : public StreamPanner {
         int    outputs[3];
         int    desired_outputs[3];
 
-        PBD::ScopedConnection speaker_connection;
-
         VBAPSpeakers& _speakers;
         
         void compute_gains (double g[3], int ls[3], int azi, int ele);
index 51430398c0a1c75ad6baa4bba973a68a7f499bfb..19dc56c9883334a3862488e4803fccba07b6f04c 100644 (file)
 #define __libardour_vbap_speakers_h__
 
 #include <string>
-#include <map>
+#include <vector>
+
+#include <boost/utility.hpp>
 
 #include <pbd/signals.h>
 
 #include "ardour/panner.h"
+#include "ardour/speakers.h"
 
 namespace ARDOUR {
 
-class VBAPSpeakers {
-  public:
-        struct cart_vec {
-            double x;
-            double y;
-            double z;
-        };
-        
-        struct ang_vec {
-            double azi;
-            double ele;
-            double length;
-        };
+class Speakers;
 
-        static const int MAX_TRIPLET_AMOUNT = 60;
+class VBAPSpeakers : public boost::noncopyable {
+  public:
         typedef std::vector<double> dvector;
 
-        VBAPSpeakers ();
-        ~VBAPSpeakers ();
-        
-        int  add_speaker (double direction, double elevation = 0.0);
-        void remove_speaker (int id);
-        void move_speaker (int id, double direction, double elevation = 0.0);
-        void clear_speakers ();
-
         const dvector matrix (int tuple) const  { return _matrices[tuple]; }
         int speaker_for_tuple (int tuple, int which) const { return _speaker_tuples[tuple][which]; }
 
         int           n_tuples () const  { return _matrices.size(); }
         int           dimension() const { return _dimension; }
 
-        static void angle_to_cart(ang_vec *from, cart_vec *to);
+        static VBAPSpeakers& instance (Speakers&);
 
-        PBD::Signal0<void> Changed;
+        ~VBAPSpeakers ();
 
   private:
+        static VBAPSpeakers* _instance;
         static const double MIN_VOL_P_SIDE_LGTH = 0.01;
         int   _dimension;  
+        std::vector<Speaker>& _speakers;
+        PBD::ScopedConnection speaker_connection;
 
-        /* A struct for a loudspeaker instance */
-        struct Speaker { 
-            int id;
-            cart_vec coords;
-            ang_vec angles;
-            
-            Speaker (int, double azimuth, double elevation);
-
-            void move (double azimuth, double elevation);
-        };
+        VBAPSpeakers (Speakers&);
 
         struct azimuth_sorter {
             bool operator() (const Speaker& s1, const Speaker& s2) {
-                    return s1.angles.azi < s2.angles.azi;
+                    return s1.angles().azi < s2.angles().azi;
             }
         };
 
@@ -96,7 +74,6 @@ class VBAPSpeakers {
           tmatrix() : dvector (3, 0.0) {}
         };
 
-        std::vector<Speaker>  _speakers;
         std::vector<dvector>  _matrices;       /* holds matrices for a given speaker combinations */
         std::vector<tmatrix>  _speaker_tuples; /* holds speakers IDs for a given combination */
 
@@ -107,11 +84,11 @@ class VBAPSpeakers {
             struct ls_triplet_chain *next;
         };
 
-        static float vec_angle(cart_vec v1, cart_vec v2);
-        static float vec_length(cart_vec v1);
-        static float vec_prod(cart_vec v1, cart_vec v2);
+        static float vec_angle(PBD::CartesianVector v1, PBD::CartesianVector v2);
+        static float vec_length(PBD::CartesianVector v1);
+        static float vec_prod(PBD::CartesianVector v1, PBD::CartesianVector v2);
         static float vol_p_side_lgth(int i, int j,int k, const std::vector<Speaker>&);
-        static void  cross_prod(cart_vec v1,cart_vec v2, cart_vec *res);
+        static void  cross_prod(PBD::CartesianVector v1,PBD::CartesianVector v2, PBD::CartesianVector *res);
 
         void update ();
         int  any_ls_inside_triplet (int a, int b, int c);
@@ -123,7 +100,6 @@ class VBAPSpeakers {
         void sort_2D_lss (int* sorted_lss);
         int  calc_2D_inv_tmatrix (double azi1,double azi2, double* inv_mat);
         
-        void dump_speakers (std::ostream&);
 };
 
 } /* namespace */
index c44ab96a59f6551586e5db6d9ad04aa221657a13..e438742266100a9c6f5591f518c5b3cb26cc4ab2 100644 (file)
@@ -61,15 +61,14 @@ 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.
 */
 
-static pan_t direct_control_to_pan (double fract)
+static double direct_control_to_stereo_pan (double fract)
 {
-       return fract;
+       return BaseStereoPanner::lr_fract_to_azimuth (fract);
 }
 
 StreamPanner::StreamPanner (Panner& p, Evoral::Parameter param)
@@ -82,10 +81,6 @@ StreamPanner::StreamPanner (Panner& p, Evoral::Parameter param)
 
        /* get our AutomationControl from our parent Panner, creating it if required */
        _control = boost::dynamic_pointer_cast<AutomationControl> (parent.control (param, true));
-
-       _x = 0.5;
-       _y = 0.5;
-       _z = 0.5;
 }
 
 StreamPanner::~StreamPanner ()
@@ -104,7 +99,7 @@ StreamPanner::set_mono (bool yn)
 void
 Panner::PanControllable::set_value (double val)
 {
-       panner.streampanner (parameter().id()).set_position (direct_control_to_pan (val));
+       panner.streampanner (parameter().id()).set_position (AngularVector (direct_control_to_stereo_pan (val), 0.0));
        AutomationControl::set_value(val);
 }
 
@@ -124,47 +119,14 @@ 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)
+StreamPanner::set_position (const AngularVector& av, bool link_call)
 {
        if (!link_call && parent.linked()) {
-               parent.set_position (xpos, ypos, *this);
+               parent.set_position (av, *this);
        }
 
-       if (_x != xpos || _y != ypos) {
-               _x = xpos;
-               _y = ypos;
-               update ();
-               Changed ();
-               _control->Changed ();
-       }
-}
-
-void
-StreamPanner::set_position (float xpos, float ypos, float zpos, bool link_call)
-{
-       if (!link_call && parent.linked()) {
-               parent.set_position (xpos, ypos, zpos, *this);
-       }
-
-       if (_x != xpos || _y != ypos || _z != zpos) {
-               _x = xpos;
-               _y = ypos;
-               _z = zpos;
+        if (_angles != av) {
+                _angles = av;
                update ();
                Changed ();
                _control->Changed ();
@@ -431,10 +393,12 @@ EqualPowerStereoPanner::update ()
           overhead.
        */
 
-       /* x == 0 => hard left
-          x == 1 => hard right
+       /* x == 0 => hard left = 180.0 degrees
+          x == 1 => hard right = 0.0 degrees
        */
 
+        double _x = BaseStereoPanner::azimuth_to_lr_fract (_angles.azi);
+
        float const panR = _x;
        float const panL = 1 - panR;
 
@@ -444,7 +408,7 @@ EqualPowerStereoPanner::update ()
        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);
 }
 
@@ -472,7 +436,7 @@ EqualPowerStereoPanner::do_distribute_automated (AudioBuffer& srcbuf, BufferSet&
        /* store effective pan position. do this even if we are muted */
 
        if (nframes > 0) {
-               effective_x = buffers[0][nframes-1];
+               _effective_angles.azi = BaseStereoPanner::lr_fract_to_azimuth (buffers[0][nframes-1]);
        }
 
        if (_muted) {
@@ -519,7 +483,7 @@ EqualPowerStereoPanner::do_distribute_automated (AudioBuffer& srcbuf, BufferSet&
 }
 
 StreamPanner*
-EqualPowerStereoPanner::factory (Panner& parent, Evoral::Parameter param)
+EqualPowerStereoPanner::factory (Panner& parent, Evoral::Parameter param, Speakers& /* ignored */)
 {
        return new EqualPowerStereoPanner (parent, param);
 }
@@ -537,8 +501,8 @@ EqualPowerStereoPanner::state (bool /*full_state*/)
        char buf[64];
        LocaleGuard lg (X_("POSIX"));
 
-       snprintf (buf, sizeof (buf), "%.12g", _x);
-       root->add_property (X_("x"), buf);
+       snprintf (buf, sizeof (buf), "%.12g", _angles.azi);
+        root->add_property (X_("azimuth"), buf);
        root->add_property (X_("type"), EqualPowerStereoPanner::name);
 
        // XXX: dont save automation here... its part of the automatable panner now.
@@ -556,10 +520,15 @@ EqualPowerStereoPanner::set_state (const XMLNode& node, int version)
        const XMLProperty* prop;
        LocaleGuard lg (X_("POSIX"));
 
-       if ((prop = node.property (X_("x")))) {
-               const float pos = atof (prop->value().c_str());
-               set_position (pos, true);
-       }
+       if ((prop = node.property (X_("azimuth")))) {
+               AngularVector a (atof (prop->value().c_str()), 0.0);
+                set_position (a, true);
+        } else if ((prop = node.property (X_("x")))) {
+                /* old school cartesian positioning */
+                AngularVector a;
+                a.azi = BaseStereoPanner::lr_fract_to_azimuth (atof (prop->value().c_str()));
+                set_position (a, true);
+        }
 
        StreamPanner::set_state (node, version);
 
@@ -575,7 +544,8 @@ EqualPowerStereoPanner::set_state (const XMLNode& node, int version)
                        _control->alist()->set_state (*((*iter)->children().front()), version);
 
                        if (_control->alist()->automation_state() != Off) {
-                               set_position (_control->list()->eval (parent.session().transport_frame()));
+                                double degrees = BaseStereoPanner::lr_fract_to_azimuth (_control->list()->eval (parent.session().transport_frame()));
+                               set_position (AngularVector (degrees, 0.0));
                        }
                }
        }
@@ -583,190 +553,6 @@ EqualPowerStereoPanner::set_state (const XMLNode& node, int version)
        return 0;
 }
 
-/*----------------------------------------------------------------------*/
-
-Multi2dPanner::Multi2dPanner (Panner& p, Evoral::Parameter param)
-       : StreamPanner (p, param)
-{
-       update ();
-}
-
-Multi2dPanner::~Multi2dPanner ()
-{
-}
-
-void
-Multi2dPanner::update ()
-{
-       static const float BIAS = FLT_MIN;
-       uint32_t i;
-       uint32_t const nouts = parent.nouts ();
-       float dsq[nouts];
-       float f, fr;
-       vector<pan_t> pans;
-
-       f = 0.0f;
-
-       for (i = 0; i < nouts; i++) {
-               dsq[i] = ((_x - parent.output(i).x) * (_x - parent.output(i).x) + (_y - parent.output(i).y) * (_y - parent.output(i).y) + BIAS);
-               if (dsq[i] < 0.0) {
-                       dsq[i] = 0.0;
-               }
-               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.output(i).desired_pan = 1.0f - (dsq[i] * fr);
-       }
-
-       effective_x = _x;
-}
-
-void
-Multi2dPanner::do_distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, nframes_t nframes)
-{
-       Sample* dst;
-       pan_t pan;
-
-       if (_muted) {
-               return;
-       }
-
-       Sample* const src = srcbuf.data();
-
-       uint32_t const N = parent.nouts ();
-       for (uint32_t n = 0; n < N; ++n) {
-               Panner::Output& o = parent.output (n);
-
-               dst = obufs.get_audio(n).data();
-
-#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 */
-
-                       nframes_t limit = min ((nframes_t)64, nframes);
-                       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;
-                       mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
-
-               } else {
-
-#else
-                       pan = o.desired_pan;
-
-                       if ((pan *= gain_coeff) != 1.0f) {
-
-                               if (pan != 0.0f) {
-                                       mix_buffers_with_gain(dst,src,nframes,pan);
-                               }
-                               
-                       } else {
-                                       mix_buffers_no_gain(dst,src,nframes);
-                       }
-#endif
-#ifdef CAN_INTERP
-               }
-#endif
-       }
-
-       return;
-}
-
-void
-Multi2dPanner::do_distribute_automated (AudioBuffer& /*src*/, BufferSet& /*obufs*/,
-                                       nframes_t /*start*/, nframes_t /*end*/, nframes_t /*nframes*/,
-                                       pan_t** /*buffers*/)
-{
-       if (_muted) {
-               return;
-       }
-
-       /* what ? */
-
-       return;
-}
-
-StreamPanner*
-Multi2dPanner::factory (Panner& p, Evoral::Parameter param)
-{
-       return new Multi2dPanner (p, param);
-}
-
-int
-Multi2dPanner::load (istream& /*in*/, string /*path*/, uint32_t& /*linecnt*/)
-{
-       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);
-
-       /* XXX no meaningful automation yet */
-
-       return *root;
-}
-
-int
-Multi2dPanner::set_state (const XMLNode& node, int /*version*/)
-{
-       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)
        : SessionObject (s, name)
        , Automatable (s)
@@ -827,16 +613,20 @@ Panner::reset_to_default ()
        }
 
        if (outputs.size() == 2) {
+                AngularVector a;
                switch (_streampanners.size()) {
                case 1:
-                       _streampanners.front()->set_position (0.5);
+                        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:
-                       _streampanners.front()->set_position (0.0);
+                        a.azi = 180.0; /* "left", in degrees */
+                       _streampanners.front()->set_position (a);
                        _streampanners.front()->pan_control()->list()->reset_default (0.0);
-                       _streampanners.back()->set_position (1.0);
+                        a.azi = 0.0; /* "right", in degrees */
+                       _streampanners.back()->set_position (a);
                        _streampanners.back()->pan_control()->list()->reset_default (1.0);
                        return;
                default:
@@ -848,13 +638,15 @@ Panner::reset_to_default ()
        vector<StreamPanner*>::iterator p;
 
        for (o = outputs.begin(), p = _streampanners.begin(); o != outputs.end() && p != _streampanners.end(); ++o, ++p) {
-               (*p)->set_position ((*o).x, (*o).y);
+               (*p)->set_position ((*o).position);
        }
 }
 
 void
 Panner::reset_streampanner (uint32_t which)
 {
+        AngularVector a;
+
        if (which >= _streampanners.size() || which >= outputs.size()) {
                return;
        }
@@ -868,24 +660,27 @@ Panner::reset_streampanner (uint32_t which)
                switch (_streampanners.size()) {
                case 1:
                        /* stereo out, 1 stream, default = middle */
-                       _streampanners.front()->set_position (0.5);
+                        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) {
-                               _streampanners.front()->set_position (0.0);
-                               _streampanners.front()->pan_control()->list()->reset_default (0.0);
-                       } else {
-                               _streampanners.back()->set_position (1.0);
-                               _streampanners.back()->pan_control()->list()->reset_default (1.0);
+                                a.azi = 180.0; /* "left", in degrees */
+                                _streampanners.front()->set_position (a);
+                                _streampanners.front()->pan_control()->list()->reset_default (0.0);
+                        } else {
+                                a.azi = 0.0; /* "right", in degrees */
+                                _streampanners.back()->set_position (a);
+                                _streampanners.back()->pan_control()->list()->reset_default (1.0);
                        }
                        break;
                }
                return;
 
        default:
-               _streampanners[which]->set_position (outputs[which].x, outputs[which].y);
+               _streampanners[which]->set_position (outputs[which].position);
        }
 }
 
@@ -947,16 +742,13 @@ Panner::reset (uint32_t nouts, uint32_t npans)
                break;
 
        case 2: // line
-               outputs.push_back (Output (0, 0));
-               outputs.push_back (Output (1.0, 0));
+               outputs.push_back (Output (AngularVector (180.0, 0.0)));
+               outputs.push_back (Output (AngularVector (0.0, 0,0)));
                for (n = 0; n < npans; ++n) {
                        _streampanners.push_back (new EqualPowerStereoPanner (*this, Evoral::Parameter(PanAutomation, 0, n)));
                }
                 break;
 
-        case 3:
-        case 4:
-        case 5:
         default:
                 setup_speakers (nouts);
                for (n = 0; n < npans; ++n) {
@@ -983,24 +775,46 @@ Panner::reset (uint32_t nouts, uint32_t npans)
        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;
 
-               _streampanners.front()->get_position (left);
-               _streampanners.back()->get_position (right);
+               left = _streampanners.front()->get_position ();
+               right = _streampanners.back()->get_position ();
 
-               if (changed || ((left == 0.5) && (right == 0.5))) {
+                if (changed || ((left.azi == 0.0) && (right.azi == 0.0))) {
 
-                       _streampanners.front()->set_position (0.0);
+                       _streampanners.front()->set_position (AngularVector (180.0, 0.0));
                        _streampanners.front()->pan_control()->list()->reset_default (0.0);
 
-                       _streampanners.back()->set_position (1.0);
+                       _streampanners.back()->set_position (AngularVector (0.0, 0.0));
                        _streampanners.back()->pan_control()->list()->reset_default (1.0);
                }
-       }
+
+       } 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
+                 */
+
+                if (npans % 2) {
+                        deg = 90.0 - degree_step;
+                } else {
+                        deg = 90.0;
+                }
+
+                for (std::vector<StreamPanner*>::iterator x = _streampanners.begin(); x != _streampanners.end(); ++x) {
+                        (*x)->set_position (AngularVector (deg, 0.0));
+                        deg += degree_step;
+                }
+        }
 }
 
 void
@@ -1076,12 +890,12 @@ Panner::automation_style () const
 struct PanPlugins {
     string name;
     uint32_t nouts;
-    StreamPanner* (*factory)(Panner&, Evoral::Parameter);
+    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 }
 };
 
@@ -1104,10 +918,10 @@ Panner::state (bool full)
 
        for (vector<Panner::Output>::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);
+               snprintf (buf, sizeof (buf), "%.12g", (*o).position.azi);
+               onode->add_property (X_("azimuth"), buf);
+               snprintf (buf, sizeof (buf), "%.12g", (*o).position.ele);
+               onode->add_property (X_("elevation"), buf);
                node->add_child_nocopy (*onode);
        }
 
@@ -1143,7 +957,6 @@ Panner::set_state (const XMLNode& node, int version)
                set_linked (string_is_affirmative (prop->value()));
        }
 
-
        if ((prop = node.property (X_("bypassed"))) != 0) {
                set_bypassed (string_is_affirmative (prop->value()));
        }
@@ -1158,15 +971,20 @@ Panner::set_state (const XMLNode& node, int version)
        for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
                if ((*niter)->name() == X_("Output")) {
 
-                       float x, y;
+                        AngularVector a;
 
-                       prop = (*niter)->property (X_("x"));
-                       sscanf (prop->value().c_str(), "%g", &x);
+                       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()));
+                        }
 
-                       prop = (*niter)->property (X_("y"));
-                       sscanf (prop->value().c_str(), "%g", &y);
+                       if ((prop = (*niter)->property (X_("elevation")))) {
+                                sscanf (prop->value().c_str(), "%lg", &a.ele);
+                        }
 
-                       outputs.push_back (Output (x, y));
+                       outputs.push_back (Output (a));
                }
        }
 
@@ -1185,7 +1003,7 @@ Panner::set_state (const XMLNode& node, int version)
                                                   assumption, but it's still an assumption.
                                                */
 
-                                               sp = pan_plugins[i].factory (*this, Evoral::Parameter(PanAutomation, 0, num_panners));
+                                               sp = pan_plugins[i].factory (*this, Evoral::Parameter(PanAutomation, 0, num_panners), _session.get_speakers ());
                                                num_panners++;
 
                                                if (sp->set_state (**niter, version) == 0) {
@@ -1244,25 +1062,21 @@ 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;
+        AngularVector delta;
+        AngularVector new_position;
 
-       orig.get_position (xnow);
-       xdelta = xpos - xnow;
+        delta = orig.get_position() - a;
 
        if (_link_direction == SameDirection) {
 
                for (vector<StreamPanner*>::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) {
                        if (*i == &orig) {
-                               (*i)->set_position (xpos, true);
+                               (*i)->set_position (a, true);
                        } else {
-                               (*i)->get_position (xnow);
-                               xnew = min (1.0f, xnow + xdelta);
-                               xnew = max (0.0f, xnew);
-                               (*i)->set_position (xnew, true);
+                                new_position = (*i)->get_position() + delta;
+                               (*i)->set_position (new_position, true);
                        }
                }
 
@@ -1270,117 +1084,10 @@ Panner::set_position (float xpos, StreamPanner& orig)
 
                for (vector<StreamPanner*>::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) {
                        if (*i == &orig) {
-                               (*i)->set_position (xpos, true);
+                               (*i)->set_position (a, 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<StreamPanner*>::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) {
-                       if (*i == &orig) {
-                               (*i)->set_position (xpos, ypos, 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);
-                       }
-               }
-
-       } else {
-
-               for (vector<StreamPanner*>::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) {
-                       if (*i == &orig) {
-                               (*i)->set_position (xpos, ypos, 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);
-                       }
-               }
-       }
-}
-
-void
-Panner::set_position (float xpos, float ypos, float zpos, StreamPanner& orig)
-{
-       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<StreamPanner*>::iterator i = _streampanners.begin(); i != _streampanners.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<StreamPanner*>::iterator i = _streampanners.begin(); i != _streampanners.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);
+                                new_position = (*i)->get_position() - delta;
+                               (*i)->set_position (new_position, true);
                        }
                }
        }
@@ -1638,47 +1345,45 @@ Panner::setup_speakers (uint32_t nouts)
 {
         switch (nouts) {
         case 3:
-               outputs.push_back (Output  (0.5, 0));
-               outputs.push_back (Output  (0, 1.0));
-               outputs.push_back (Output  (1.0, 1.0));
+                /* 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 (Output  (0, 1.0));
-               outputs.push_back (Output  (1.0, 1.0));
-               outputs.push_back (Output  (1.0, 0));
-               outputs.push_back (Output  (0, 0));
+                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;
 
-       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));
-                break;
-
-       default:
-               /* XXX horrible placement. FIXME */
-               for (uint32_t n = 0; n < nouts; ++n) {
-                       outputs.push_back (Output (0.1 * n, 0.1 * n));
+       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)));
                }
         }
+        }
 
-        VBAPSpeakers& speakers (_session.get_speakers());
-
+        Speakers& speakers (_session.get_speakers());
+                        
         speakers.clear_speakers ();
 
         for (vector<Output>::iterator o = outputs.begin(); o != outputs.end(); ++o) {
-                double azimuth;
-                double elevation;
-                double tx, ty, tz;
-
-                tx = (*o).x - 0.5;
-                ty = (*o).y - 0.5;
-                tz = 0.0; // XXX change this if we ever do actual 3D
-
-                cart_to_azi_ele (tx, ty, tz, azimuth, elevation);
-                speakers.add_speaker (azimuth, elevation);
+                speakers.add_speaker ((*o).position);
         }
 }
index 64c5a173c4e02597dd6d3139fe450cadc9e4e3c4..06fd2ec41d61c0807d5e3322ae47a3b74feeded6 100644 (file)
@@ -4202,11 +4202,12 @@ Session::ensure_search_path_includes (const string& path, DataType type)
         }
 }
 
-VBAPSpeakers&
+Speakers&
 Session::get_speakers() 
 {
         if (!_speakers) {
-                _speakers = new VBAPSpeakers;
+                _speakers = new Speakers;
         }
+
         return *_speakers;
 }
diff --git a/libs/ardour/speakers.cc b/libs/ardour/speakers.cc
new file mode 100644 (file)
index 0000000..c5495f2
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+    Copyright (C) 2010 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
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "ardour/speaker.h"
+#include "ardour/speakers.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+using namespace std;
+
+Speaker::Speaker (int i, const AngularVector& position)
+        : id (i)
+{
+        move (position);
+}
+
+void
+Speaker::move (const AngularVector& new_position)
+{
+        _angles = new_position;
+        _angles.cartesian (_coords);
+}
+
+Speakers::Speakers ()
+{
+}
+
+Speakers::~Speakers ()
+{
+}
+
+void
+Speakers::dump_speakers (ostream& o)
+{
+        for (vector<Speaker>::iterator i = _speakers.begin(); i != _speakers.end(); ++i) {
+                o << "Speaker " << (*i).id << " @ " 
+                  << (*i).coords().x << ", " << (*i).coords().y << ", " << (*i).coords().z
+                  << " azimuth " << (*i).angles().azi
+                  << " elevation " << (*i).angles().ele
+                  << " distance " << (*i).angles().length
+                  << endl;
+        }
+}
+
+void
+Speakers::clear_speakers ()
+{
+        _speakers.clear ();
+        update ();
+}
+
+int 
+Speakers::add_speaker (const AngularVector& position)
+{
+        int id = _speakers.size();
+
+        cerr << "Added speaker " << id << " at " << position.azi << " /= " << position.ele << endl;
+
+        _speakers.push_back (Speaker (id, position));
+        update ();
+
+        dump_speakers (cerr);
+        Changed ();
+
+        return id;
+}        
+
+void
+Speakers::remove_speaker (int id)
+{
+        for (vector<Speaker>::iterator i = _speakers.begin(); i != _speakers.end(); ) {
+                if ((*i).id == id) {
+                        i = _speakers.erase (i);
+                        update ();
+                        break;
+                } 
+        }
+}
+
+void
+Speakers::move_speaker (int id, const AngularVector& new_position)
+{
+        for (vector<Speaker>::iterator i = _speakers.begin(); i != _speakers.end(); ++i) {
+                if ((*i).id == id) {
+                        (*i).move (new_position);
+                        update ();
+                        break;
+                }
+        }
+}
+
+
+
index bb5bed4963d7f4eae475b92e64bfcb8bf63fcf64..31a8e6c1345bc0bd8b3419283eafc5afc833087e 100644 (file)
@@ -41,6 +41,7 @@
 
 #include "pbd/cartesian.h"
 
+#include "ardour/speakers.h"
 #include "ardour/vbap.h"
 #include "ardour/vbap_speakers.h"
 #include "ardour/audio_buffer.h"
@@ -50,13 +51,13 @@ using namespace PBD;
 using namespace ARDOUR;
 using namespace std;
 
-VBAPanner::VBAPanner (Panner& parent, Evoral::Parameter param, VBAPSpeakers& s)
+string VBAPanner::name = X_("VBAP");
+
+VBAPanner::VBAPanner (Panner& parent, Evoral::Parameter param, Speakers& s)
         : StreamPanner (parent, param)
         , _dirty (false)
-        , _speakers (s)
-
+        , _speakers (VBAPSpeakers::instance (s))
 {
-         _speakers.Changed.connect_same_thread (speaker_connection, boost::bind (&VBAPanner::mark_dirty, this));
 }
 
 VBAPanner::~VBAPanner ()
@@ -72,16 +73,11 @@ VBAPanner::mark_dirty ()
 void
 VBAPanner::update ()
 {
-        /* convert from coordinate space with (0,0) at upper left to (0,0) at center and dimensions of 1 unit */
-        _x -= 0.5;
-        _y -= 0.5;
-
-
-        /* we're 2D for now */
-        _z = 0.0;
-
-        cart_to_azi_ele (_x, _y, _z, _azimuth, _elevation);
+        /* force 2D for now */
+        _angles.ele = 0.0;
         _dirty = true;
+
+        Changed ();
 }
 
 void 
@@ -234,3 +230,10 @@ VBAPanner::set_state (const XMLNode& node, int /*version*/)
 {
         return 0;
 }
+
+StreamPanner*
+VBAPanner::factory (Panner& parent, Evoral::Parameter param, Speakers& s)
+{
+       return new VBAPanner (parent, param, s);
+}
+
index 3881c971d8d503103ce6394a5713009f7f8d70d6..308f1beafb4b7e0598c84b789408bb98fbb7a664 100644 (file)
 #include "ardour/vbap_speakers.h"
 
 using namespace ARDOUR;
+using namespace PBD;
 using namespace std;
 
-VBAPSpeakers::Speaker::Speaker (int i, double azimuth, double elevation)
-        : id (i)
-{
-        move (azimuth, elevation);
-        cerr << setprecision (5) << "%%%%%%%%%% New speaker @ " << angles.azi << ", " << angles.ele << endl;
-}
-
-void
-VBAPSpeakers::Speaker::move (double azimuth, double elevation)
-{
-        angles.azi = azimuth;
-        angles.ele = elevation;
-        angles.length = 1.0;
-        angle_to_cart (&angles, &coords);
-}
+VBAPSpeakers* VBAPSpeakers::_instance = 0;
 
-VBAPSpeakers::VBAPSpeakers ()
-        : _dimension (2)
-{
-}
-
-VBAPSpeakers::~VBAPSpeakers ()
+VBAPSpeakers& 
+VBAPSpeakers::instance (Speakers& s)
 {
-}
-
-void
-VBAPSpeakers::dump_speakers (ostream& o)
-{
-        for (vector<Speaker>::iterator i = _speakers.begin(); i != _speakers.end(); ++i) {
-                o << "Speaker " << (*i).id << " @ " 
-                  << (*i).coords.x << ", " << (*i).coords.y << ", " << (*i).coords.z
-                  << " azimuth " << (*i).angles.azi
-                  << " elevation " << (*i).angles.ele
-                  << " distance " << (*i).angles.length
-                  << endl;
+        if (_instance == 0) {
+                _instance = new VBAPSpeakers (s);
         }
-}
 
-void
-VBAPSpeakers::clear_speakers ()
-{
-        _speakers.clear ();
-        update ();
+        return *_instance;
 }
 
-int 
-VBAPSpeakers::add_speaker (double azimuth, double elevation)
-{
-        int id = _speakers.size();
-
-        cerr << "Added speaker " << id << " at " << azimuth << " /= " << elevation << endl;
-
-        _speakers.push_back (Speaker (id, azimuth, elevation));
-        update ();
-
-        dump_speakers (cerr);
-
-        return id;
-}        
-
-void
-VBAPSpeakers::remove_speaker (int id)
+VBAPSpeakers::VBAPSpeakers (Speakers& s)
+        : _dimension (2)
+        , _speakers (s.speakers())
 {
-        for (vector<Speaker>::iterator i = _speakers.begin(); i != _speakers.end(); ) {
-                if ((*i).id == id) {
-                        i = _speakers.erase (i);
-                        update ();
-                        break;
-                } 
-        }
+        s.Changed.connect_same_thread (speaker_connection, boost::bind (&VBAPSpeakers::update, this));
 }
 
-void
-VBAPSpeakers::move_speaker (int id, double direction, double elevation)
+VBAPSpeakers::~VBAPSpeakers ()
 {
-        for (vector<Speaker>::iterator i = _speakers.begin(); i != _speakers.end(); ++i) {
-                if ((*i).id == id) {
-                        (*i).move (direction, elevation);
-                        update ();
-                        break;
-                }
-        }
 }
 
 void
 VBAPSpeakers::update ()
 {
         int dim = 2;
-
-        for (vector<Speaker>::iterator i = _speakers.begin(); i != _speakers.end(); ++i) {
-                if ((*i).angles.ele != 0.0) {
-                        cerr << "\n\n\nSPEAKER " << (*i).id << " has ele = " << (*i).angles.ele << "\n\n\n\n";
+        
+        for (vector<Speaker>::const_iterator i = _speakers.begin(); i != _speakers.end(); ++i) {
+                if ((*i).angles().ele != 0.0) {
+                        cerr << "\n\n\nSPEAKER " << (*i).id << " has ele = " << (*i).angles().ele << "\n\n\n\n";
                         dim = 3;
                         break;
                 }
@@ -157,16 +97,8 @@ VBAPSpeakers::update ()
         } else {
                 choose_speaker_pairs ();
         }
-
-        Changed (); /* EMIT SIGNAL */
 }
 
-void 
-VBAPSpeakers::angle_to_cart(ang_vec *from, cart_vec *to)
-{
-        PBD::azi_ele_to_cart (from->azi, from->ele, to->x, to->y, to->z);
-}  
-
 void 
 VBAPSpeakers::choose_speaker_triplets(struct ls_triplet_chain **ls_triplets) 
 {
@@ -217,7 +149,7 @@ VBAPSpeakers::choose_speaker_triplets(struct ls_triplet_chain **ls_triplets)
         for (i = 0;i < n_speakers; i++) { 
                 for (j = i+1; j < n_speakers; j++) { 
                         if (connections[i][j] == 1) {
-                                distance = fabs(vec_angle(_speakers[i].coords,_speakers[j].coords));
+                                distance = fabs(vec_angle(_speakers[i].coords(),_speakers[j].coords()));
                                 k=0;
                                 while(distance_table[k] < distance) {
                                         k++;
@@ -291,7 +223,9 @@ VBAPSpeakers::any_ls_inside_triplet(int a, int b, int c)
 {
         /* returns 1 if there is loudspeaker(s) inside given ls triplet */
         float invdet;
-        cart_vec *lp1, *lp2, *lp3;
+        const CartesianVector* lp1;
+        const CartesianVector* lp2;
+        const CartesianVector* lp3;
         float invmx[9];
         int i,j;
         float tmp;
@@ -299,9 +233,9 @@ VBAPSpeakers::any_ls_inside_triplet(int a, int b, int c)
         bool this_inside;
         int n_speakers = _speakers.size();
 
-        lp1 =  &(_speakers[a].coords);
-        lp2 =  &(_speakers[b].coords);
-        lp3 =  &(_speakers[c].coords);
+        lp1 =  &(_speakers[a].coords());
+        lp2 =  &(_speakers[b].coords());
+        lp3 =  &(_speakers[c].coords());
         
         /* matrix inversion */
         invdet = 1.0 / (  lp1->x * ((lp2->y * lp3->z) - (lp2->z * lp3->y))
@@ -323,9 +257,9 @@ VBAPSpeakers::any_ls_inside_triplet(int a, int b, int c)
                 if (i != a && i!=b && i != c) {
                         this_inside = true;
                         for (j = 0; j < 3; j++) {
-                                tmp = _speakers[i].coords.x * invmx[0 + j*3];
-                                tmp += _speakers[i].coords.y * invmx[1 + j*3];
-                                tmp += _speakers[i].coords.z * invmx[2 + j*3];
+                                tmp = _speakers[i].coords().x * invmx[0 + j*3];
+                                tmp += _speakers[i].coords().y * invmx[1 + j*3];
+                                tmp += _speakers[i].coords().z * invmx[2 + j*3];
                                 if (tmp < -0.001) {
                                         this_inside = false;
                                 }
@@ -369,7 +303,7 @@ VBAPSpeakers::add_ldsp_triplet(int i, int j, int k, struct ls_triplet_chain **ls
 }
 
 float 
-VBAPSpeakers::vec_angle(cart_vec v1, cart_vec v2)
+VBAPSpeakers::vec_angle(CartesianVector v1, CartesianVector v2)
 {
         float inner= ((v1.x*v2.x + v1.y*v2.y + v1.z*v2.z)/
                       (vec_length(v1) * vec_length(v2)));
@@ -386,13 +320,13 @@ VBAPSpeakers::vec_angle(cart_vec v1, cart_vec v2)
 }
 
 float 
-VBAPSpeakers::vec_length(cart_vec v1)
+VBAPSpeakers::vec_length(CartesianVector v1)
 {
         return (sqrt(v1.x*v1.x + v1.y*v1.y + v1.z*v1.z));
 }
 
 float 
-VBAPSpeakers::vec_prod(cart_vec v1, cart_vec v2)
+VBAPSpeakers::vec_prod(CartesianVector v1, CartesianVector v2)
 {
         return (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z);
 }
@@ -405,13 +339,13 @@ VBAPSpeakers::vol_p_side_lgth(int i, int j,int k, const vector<Speaker>& speaker
            This is used when removing too narrow triangles. */
         
         float volper, lgth;
-        cart_vec xprod;
+        CartesianVector xprod;
 
-        cross_prod (speakers[i].coords, speakers[j].coords, &xprod);
-        volper = fabsf (vec_prod(xprod, speakers[k].coords));
-        lgth = (fabsf (vec_angle(speakers[i].coords, speakers[j].coords)) 
-                + fabsf (vec_angle(speakers[i].coords, speakers[k].coords)) 
-                + fabsf (vec_angle(speakers[j].coords, speakers[k].coords)));
+        cross_prod (speakers[i].coords(), speakers[j].coords(), &xprod);
+        volper = fabsf (vec_prod(xprod, speakers[k].coords()));
+        lgth = (fabsf (vec_angle(speakers[i].coords(), speakers[j].coords())) 
+                + fabsf (vec_angle(speakers[i].coords(), speakers[k].coords())) 
+                + fabsf (vec_angle(speakers[j].coords(), speakers[k].coords())));
 
         if (lgth > 0.00001) {
                 return volper / lgth;
@@ -421,7 +355,7 @@ VBAPSpeakers::vol_p_side_lgth(int i, int j,int k, const vector<Speaker>& speaker
 }
 
 void 
-VBAPSpeakers::cross_prod(cart_vec v1,cart_vec v2, cart_vec *res) 
+VBAPSpeakers::cross_prod(CartesianVector v1,CartesianVector v2, CartesianVector *res) 
 {
         float length;
 
@@ -429,7 +363,7 @@ VBAPSpeakers::cross_prod(cart_vec v1,cart_vec v2, cart_vec *res)
         res->y = (v1.z * v2.x ) - (v1.x * v2.z);
         res->z = (v1.x * v2.y ) - (v1.y * v2.x);
         
-        length= vec_length(*res);
+        length = vec_length(*res);
         res->x /= length;
         res->y /= length;
         res->z /= length;
@@ -446,30 +380,30 @@ VBAPSpeakers::lines_intersect (int i, int j, int k, int l)
            if you want to have that paper.
         */
 
-        cart_vec v1;
-        cart_vec v2;
-        cart_vec v3, neg_v3;
+        CartesianVector v1;
+        CartesianVector v2;
+        CartesianVector v3, neg_v3;
         float dist_ij,dist_kl,dist_iv3,dist_jv3,dist_inv3,dist_jnv3;
         float dist_kv3,dist_lv3,dist_knv3,dist_lnv3;
         
-        cross_prod(_speakers[i].coords,_speakers[j].coords,&v1);
-        cross_prod(_speakers[k].coords,_speakers[l].coords,&v2);
+        cross_prod(_speakers[i].coords(),_speakers[j].coords(),&v1);
+        cross_prod(_speakers[k].coords(),_speakers[l].coords(),&v2);
         cross_prod(v1,v2,&v3);
         
         neg_v3.x= 0.0 - v3.x;
         neg_v3.y= 0.0 - v3.y;
         neg_v3.z= 0.0 - v3.z;
 
-        dist_ij = (vec_angle(_speakers[i].coords,_speakers[j].coords));
-        dist_kl = (vec_angle(_speakers[k].coords,_speakers[l].coords));
-        dist_iv3 = (vec_angle(_speakers[i].coords,v3));
-        dist_jv3 = (vec_angle(v3,_speakers[j].coords));
-        dist_inv3 = (vec_angle(_speakers[i].coords,neg_v3));
-        dist_jnv3 = (vec_angle(neg_v3,_speakers[j].coords));
-        dist_kv3 = (vec_angle(_speakers[k].coords,v3));
-        dist_lv3 = (vec_angle(v3,_speakers[l].coords));
-        dist_knv3 = (vec_angle(_speakers[k].coords,neg_v3));
-        dist_lnv3 = (vec_angle(neg_v3,_speakers[l].coords));
+        dist_ij = (vec_angle(_speakers[i].coords(),_speakers[j].coords()));
+        dist_kl = (vec_angle(_speakers[k].coords(),_speakers[l].coords()));
+        dist_iv3 = (vec_angle(_speakers[i].coords(),v3));
+        dist_jv3 = (vec_angle(v3,_speakers[j].coords()));
+        dist_inv3 = (vec_angle(_speakers[i].coords(),neg_v3));
+        dist_jnv3 = (vec_angle(neg_v3,_speakers[j].coords()));
+        dist_kv3 = (vec_angle(_speakers[k].coords(),v3));
+        dist_lv3 = (vec_angle(v3,_speakers[l].coords()));
+        dist_knv3 = (vec_angle(_speakers[k].coords(),neg_v3));
+        dist_lnv3 = (vec_angle(neg_v3,_speakers[l].coords()));
 
         /* if one of loudspeakers is close to crossing point, don't do anything*/
 
@@ -496,7 +430,9 @@ VBAPSpeakers::calculate_3x3_matrixes(struct ls_triplet_chain *ls_triplets)
 {  
         /* Calculates the inverse matrices for 3D */
         float invdet;
-        cart_vec *lp1, *lp2, *lp3;
+        const CartesianVector* lp1;
+        const CartesianVector* lp2;
+        const CartesianVector* lp3;
         float *invmx;
         struct ls_triplet_chain *tr_ptr = ls_triplets;
         int triplet_count = 0;
@@ -524,9 +460,9 @@ VBAPSpeakers::calculate_3x3_matrixes(struct ls_triplet_chain *ls_triplets)
         }
 
         while (tr_ptr != 0) {
-                lp1 =  &(_speakers[tr_ptr->ls_nos[0]].coords);
-                lp2 =  &(_speakers[tr_ptr->ls_nos[1]].coords);
-                lp3 =  &(_speakers[tr_ptr->ls_nos[2]].coords);
+                lp1 =  &(_speakers[tr_ptr->ls_nos[0]].coords());
+                lp2 =  &(_speakers[tr_ptr->ls_nos[1]].coords());
+                lp3 =  &(_speakers[tr_ptr->ls_nos[2]].coords());
                 
                 /* matrix inversion */
                 invmx = tr_ptr->inv_mx;
@@ -603,17 +539,17 @@ VBAPSpeakers::choose_speaker_pairs (){
         for (speaker = 0; speaker < n_speakers-1; speaker++) {
 
                 cerr << "Looking at " 
-                     << _speakers[sorted_speakers[speaker]].id << " @ " << _speakers[sorted_speakers[speaker]].angles.azi  
+                     << _speakers[sorted_speakers[speaker]].id << " @ " << _speakers[sorted_speakers[speaker]].angles().azi  
                      << " and "
-                     << _speakers[sorted_speakers[speaker+1]].id << " @ " << _speakers[sorted_speakers[speaker+1]].angles.azi  
+                     << _speakers[sorted_speakers[speaker+1]].id << " @ " << _speakers[sorted_speakers[speaker+1]].angles().azi  
                      << " delta = " 
-                     << _speakers[sorted_speakers[speaker+1]].angles.azi - _speakers[sorted_speakers[speaker]].angles.azi
+                     << _speakers[sorted_speakers[speaker+1]].angles().azi - _speakers[sorted_speakers[speaker]].angles().azi
                      << endl;
 
-                if ((_speakers[sorted_speakers[speaker+1]].angles.azi - 
-                     _speakers[sorted_speakers[speaker]].angles.azi) <= AZIMUTH_DELTA_THRESHOLD_DEGREES) {
-                        if (calc_2D_inv_tmatrix( _speakers[sorted_speakers[speaker]].angles.azi, 
-                                                 _speakers[sorted_speakers[speaker+1]].angles.azi, 
+                if ((_speakers[sorted_speakers[speaker+1]].angles().azi - 
+                     _speakers[sorted_speakers[speaker]].angles().azi) <= AZIMUTH_DELTA_THRESHOLD_DEGREES) {
+                        if (calc_2D_inv_tmatrix( _speakers[sorted_speakers[speaker]].angles().azi, 
+                                                 _speakers[sorted_speakers[speaker+1]].angles().azi, 
                                                  inverse_matrix[speaker]) != 0){
                                 exists[speaker] = true;
                                 expected_pairs++;
@@ -621,10 +557,10 @@ VBAPSpeakers::choose_speaker_pairs (){
                 }
         }
         
-        if (((6.283 - _speakers[sorted_speakers[n_speakers-1]].angles.azi) 
-             +_speakers[sorted_speakers[0]].angles.azi) <= AZIMUTH_DELTA_THRESHOLD_DEGREES) {
-                if (calc_2D_inv_tmatrix(_speakers[sorted_speakers[n_speakers-1]].angles.azi, 
-                                       _speakers[sorted_speakers[0]].angles.azi, 
+        if (((6.283 - _speakers[sorted_speakers[n_speakers-1]].angles().azi) 
+             +_speakers[sorted_speakers[0]].angles().azi) <= AZIMUTH_DELTA_THRESHOLD_DEGREES) {
+                if (calc_2D_inv_tmatrix(_speakers[sorted_speakers[n_speakers-1]].angles().azi, 
+                                        _speakers[sorted_speakers[0]].angles().azi, 
                                        inverse_matrix[n_speakers-1]) != 0) { 
                         exists[n_speakers-1] = true;
                         expected_pairs++;
index e6417d7f528979505dc395461746822883cc4d02..3ece2d0784336b583b0884eeaed284c61749ee96 100644 (file)
@@ -189,6 +189,7 @@ libardour_sources = [
        'sndfilesource.cc',
        'source.cc',
        'source_factory.cc',
+        'speakers.cc',
        'strip_silence.cc',
        'svn_revision.cc',
        'tape_file_matcher.cc',
index fe7bf29acfc512b3e17d36d7a2f31e2145cf2f99..8bece8e0058638defa73fdd0c742ca33d04f39ae 100644 (file)
@@ -18,6 +18,7 @@
 
 #include <iostream>
 #include <math.h>
+
 #include "pbd/cartesian.h"
 
 using namespace std;
index 67f8f0629cc53a834cdfcb97d9f4740ffc53e6bf..b44a12cd3dc58c5406d7ff2648c13633be055ec1 100644 (file)
 #ifndef __libpbd_cartesian_h__
 #define __libpbd_cartesian_h__
 
+#include <cfloat>
+#include <cmath>
+
 namespace PBD {
-        void azi_ele_to_cart (double azi, double ele, double& x, double& y, double& z);
-        void cart_to_azi_ele (double x, double y, double z, double& azi, double& ele);
+
+void azi_ele_to_cart (double azi, double ele, double& x, double& y, double& z);
+void cart_to_azi_ele (double x, double y, double z, double& azi, double& ele);
+        
+struct AngularVector;
+
+struct CartesianVector {
+    double x;
+    double y;
+    double z;
+
+    CartesianVector () : x(0.0), y(0.0), z(0.0) {}
+    CartesianVector (double xp, double yp, double zp = 0.0) : x(xp), y(yp), z(zp) {}
+
+    CartesianVector& translate (CartesianVector& other, double xtranslate, double ytranslate, double ztranslate = 0.0) {
+            other.x += xtranslate;
+            other.y += ytranslate;
+            other.z += ztranslate;
+            return other;
+    }
+
+    CartesianVector& scale (CartesianVector& other, double xscale, double yscale, double zscale = 1.0) {
+            other.x *= xscale;
+            other.y *= yscale;
+            other.z *= zscale;
+            return other;
+    }
+
+    void angular (AngularVector&) const;
+};
+
+struct AngularVector {
+    double azi;
+    double ele;
+    double length;
+
+    AngularVector () : azi(0.0), ele(0.0), length (0.0) {}
+    AngularVector (double a, double e, double l = 1.0) : azi(a), ele(e), length (l) {}
+
+    AngularVector operator- (const AngularVector& other) const {
+            AngularVector r;
+            r.azi = azi - other.azi;
+            r.ele = ele - other.ele;
+            r.length = length - other.length;
+            return r;
+    }
+
+    AngularVector operator+ (const AngularVector& other) const {
+            AngularVector r;
+            r.azi = azi + other.azi;
+            r.ele = ele + other.ele;
+            r.length = length + other.length;
+            return r;
+    }
+
+    bool operator== (const AngularVector& other) const {
+            return fabs (azi - other.azi) <= FLT_EPSILON &&
+                    fabs (ele - other.ele) <= FLT_EPSILON &&
+                    fabs (length - other.length) <= FLT_EPSILON;
+    }
+
+    bool operator!= (const AngularVector& other) const {
+            return fabs (azi - other.azi) > FLT_EPSILON ||
+                    fabs (ele - other.ele) > FLT_EPSILON ||
+                    fabs (length - other.length) > FLT_EPSILON;
+    }
+
+    void cartesian (CartesianVector& c) const {
+            azi_ele_to_cart (azi, ele, c.x, c.y, c.z);
+    }
+};
+
+inline
+void CartesianVector::angular (AngularVector& a) const {
+        cart_to_azi_ele (x, y, z, a.azi, a.ele);
+}
+
 }
 
 #endif /* __libpbd_cartesian_h__ */
index 6dda801ad69829e2512976a1e1226c78424de700..c9c62dcb21da1bace83e1d4911258eda315319b7 100644 (file)
@@ -840,17 +840,13 @@ MackieControlProtocol::handle_control_event (SurfacePort & port, Control & contr
                                        if (route->panner()->npanners() == 1 || (route->panner()->npanners() == 2 && route->panner()->linked()))
                                        {
                                                // assume pan for now
-                                               float xpos;
-                                               route->panner()->streampanner (0).get_effective_position (xpos);
-
-                                               // calculate new value, and trim
-                                               xpos += state.delta * state.sign;
-                                               if (xpos > 1.0)
-                                                       xpos = 1.0;
-                                               else if (xpos < 0.0)
-                                                       xpos = 0.0;
-
-                                               route->panner()->streampanner (0).set_position (xpos);
+                                                AngularVector a = route->panner()->streampanner (0).get_effective_position ();
+                                                
+                                               // calculate new value, and adjust
+                                               a.azi += 180.0 * state.delta * state.sign;
+                                                a.azi = min (180.0, a.azi);
+                                                a.azi = max (0.0, a.azi);
+                                               route->panner()->streampanner (0).set_position (a);
                                        }
                                }
                                else
@@ -1008,13 +1004,13 @@ MackieControlProtocol::notify_panner_changed (RouteSignal * route_signal, bool f
                boost::shared_ptr<Panner> panner = route_signal->route()->panner();
                if ((panner && panner->npanners() == 1) || (panner->npanners() == 2 && panner->linked()))
                {
-                       float pos;
-                       route_signal->route()->panner()->streampanner(0).get_effective_position (pos);
+                       AngularVector pos = route_signal->route()->panner()->streampanner(0).get_effective_position ();
+                        float fract = 1.0 - (pos.azi / 180.0); /* 1.0 = 0 degrees = right; 0.0 = 180 degrees = left */
 
                        // cache the MidiByteArray here, because the mackie led control is much lower
                        // resolution than the panner control. So we save lots of byte
                        // sends in spite of more work on the comparison
-                       MidiByteArray bytes = builder.build_led_ring (pot, ControlState (on, pos), MackieMidiBuilder::midi_pot_mode_dot);
+                       MidiByteArray bytes = builder.build_led_ring (pot, ControlState (on, fract), MackieMidiBuilder::midi_pot_mode_dot);
                        // check that something has actually changed
                        if (force_update || bytes != route_signal->last_pan_written())
                        {