tentative commit of new panners subtree
authorPaul Davis <paul@linuxaudiosystems.com>
Mon, 17 Jan 2011 17:51:44 +0000 (17:51 +0000)
committerPaul Davis <paul@linuxaudiosystems.com>
Mon, 17 Jan 2011 17:51:44 +0000 (17:51 +0000)
git-svn-id: svn://localhost/ardour2/branches/3.0@8521 d708f5d6-7413-0410-9779-e7cbd77b26cf

libs/panners/1in2out/panner_1in2out.cc [new file with mode: 0644]
libs/panners/1in2out/panner_1in2out.h [new file with mode: 0644]
libs/panners/2in2out/panner_2in2out.cc [new file with mode: 0644]
libs/panners/2in2out/panner_2in2out.h [new file with mode: 0644]
libs/panners/2in2out/wscript [new file with mode: 0644]
libs/panners/vbap/vbap.cc [new file with mode: 0644]
libs/panners/vbap/vbap.h [new file with mode: 0644]
libs/panners/vbap/vbap_speakers.cc [new file with mode: 0644]
libs/panners/vbap/vbap_speakers.h [new file with mode: 0644]
libs/panners/wscript [new file with mode: 0644]

diff --git a/libs/panners/1in2out/panner_1in2out.cc b/libs/panners/1in2out/panner_1in2out.cc
new file mode 100644 (file)
index 0000000..2851aec
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+    Copyright (C) 2004-2011 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 <inttypes.h>
+
+#include <cmath>
+#include <cerrno>
+#include <fstream>
+#include <cstdlib>
+#include <string>
+#include <cstdio>
+#include <locale.h>
+#include <unistd.h>
+#include <float.h>
+#include <iomanip>
+
+#include <glibmm.h>
+
+#include "pbd/cartesian.h"
+#include "pbd/convert.h"
+#include "pbd/error.h"
+#include "pbd/failed_constructor.h"
+#include "pbd/xml++.h"
+#include "pbd/enumwriter.h"
+
+#include "evoral/Curve.hpp"
+
+#include "ardour/session.h"
+#include "ardour/panner.h"
+#include "ardour/panner_1in2out.h"
+#include "ardour/utils.h"
+#include "ardour/audio_buffer.h"
+
+#include "ardour/runtime_functions.h"
+#include "ardour/buffer_set.h"
+#include "ardour/audio_buffer.h"
+#include "ardour/vbap.h"
+
+#include "i18n.h"
+
+#include "pbd/mathfix.h"
+
+using namespace std;
+using namespace ARDOUR;
+using namespace PBD;
+
+static PanPluginDescriptor _descriptor = {
+        "Mono to Stereo Panner",
+        1, 1, 2, 2,
+        Panner1in2out::factory
+};
+
+extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; }
+
+Panner1in2out::Panner1in2out (PannerShell& p)
+       : Panner (p)
+        , _position (new PanControllable (parent.session(), _("position"), this, Evoral::Parameter(PanAzimuthAutomation, 0, 0)))
+       , left (0.5)
+       , right (0.5)
+       , left_interp (left)
+       , right_interp (right)
+{
+        desired_left = left;
+        desired_right = right;
+}
+
+Panner1in2out::~Panner1in2out ()
+{
+}
+
+void
+Panner1in2out::set_position (double p)
+{
+        _desired_right = p;
+        _desired_left = 1 - p;
+}
+
+void
+Panner1in2out::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t /* not used */)
+{
+       assert (obufs.count().n_audio() == 2);
+
+       pan_t delta;
+       Sample* dst;
+       pan_t pan;
+
+       if (_muted) {
+               return;
+       }
+
+       Sample* const src = srcbuf.data();
+        
+       /* LEFT OUTPUT */
+
+       dst = obufs.get_audio(0).data();
+
+       if (fabsf ((delta = (left[which] - desired_left[which]))) > 0.002) { // about 1 degree of arc
+
+               /* we've moving the pan by an appreciable amount, so we must
+                  interpolate over 64 frames or nframes, whichever is smaller */
+
+               pframes_t const limit = min ((pframes_t) 64, nframes);
+               pframes_t n;
+
+               delta = -(delta / (float) (limit));
+
+               for (n = 0; n < limit; n++) {
+                       left_interp[which] = left_interp[which] + delta;
+                       left = left_interp[which] + 0.9 * (left[which] - left_interp[which]);
+                       dst[n] += src[n] * left[which] * gain_coeff;
+               }
+
+               /* then pan the rest of the buffer; no need for interpolation for this bit */
+
+               pan = left[which] * gain_coeff;
+
+               mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
+
+       } else {
+
+               left[which] = desired_left[which];
+               left_interp[which] = left[which];
+
+               if ((pan = (left[which] * gain_coeff)) != 1.0f) {
+
+                       if (pan != 0.0f) {
+
+                               /* pan is 1 but also not 0, so we must do it "properly" */
+
+                               mix_buffers_with_gain(dst,src,nframes,pan);
+
+                               /* mark that we wrote into the buffer */
+
+                               // obufs[0] = 0;
+
+                       }
+
+               } else {
+
+                       /* pan is 1 so we can just copy the input samples straight in */
+
+                       mix_buffers_no_gain(dst,src,nframes);
+                        
+                       /* XXX it would be nice to mark that we wrote into the buffer */
+               }
+       }
+
+       /* RIGHT OUTPUT */
+
+       dst = obufs.get_audio(1).data();
+
+       if (fabsf ((delta = (right[which] - desired_right[which]))) > 0.002) { // about 1 degree of arc
+
+               /* we're moving the pan by an appreciable amount, so we must
+                  interpolate over 64 frames or nframes, whichever is smaller */
+
+               pframes_t const limit = min ((pframes_t) 64, nframes);
+               pframes_t n;
+
+               delta = -(delta / (float) (limit));
+
+               for (n = 0; n < limit; n++) {
+                       right_interp[which] = right_interp[which] + delta;
+                       right[which] = right_interp[which] + 0.9 * (right[which] - right_interp[which]);
+                       dst[n] += src[n] * right[which] * gain_coeff;
+               }
+
+               /* then pan the rest of the buffer, no need for interpolation for this bit */
+
+               pan = right[which] * gain_coeff;
+
+               mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
+
+               /* XXX it would be nice to mark the buffer as written to */
+
+       } else {
+
+               right[which] = desired_right[which];
+               right_interp[which] = right[which];
+
+               if ((pan = (right[which] * gain_coeff)) != 1.0f) {
+
+                       if (pan != 0.0f) {
+
+                               /* pan is not 1 but also not 0, so we must do it "properly" */
+                               
+                               mix_buffers_with_gain(dst,src,nframes,pan);
+
+                               /* XXX it would be nice to mark the buffer as written to */
+                       }
+
+               } else {
+
+                       /* pan is 1 so we can just copy the input samples straight in */
+                       
+                       mix_buffers_no_gain(dst,src,nframes);
+
+                       /* XXX it would be nice to mark the buffer as written to */
+               }
+       }
+
+}
+
+string
+Panner1in2out::describe_parameter (Evoral::Parameter param)
+{
+        switch (param.type()) {
+        case PanWidthAutomation:
+                return "Pan:width";
+        case PanAzimuthAutomation:
+                return "Pan:position";
+        case PanElevationAutomation: 
+                error << X_("stereo panner should not have elevation control") << endmsg;
+                return "Pan:elevation";
+        } 
+        
+        return Automatable::describe_parameter (param);
+}
+
diff --git a/libs/panners/1in2out/panner_1in2out.h b/libs/panners/1in2out/panner_1in2out.h
new file mode 100644 (file)
index 0000000..152eb71
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+    Copyright (C) 2004-2011 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 __ardour_panner_1in2out_h__
+#define __ardour_panner_1in2out_h__
+
+#include <cmath>
+#include <cassert>
+#include <vector>
+#include <string>
+#include <iostream>
+
+#include "pbd/stateful.h"
+#include "pbd/controllable.h"
+#include "pbd/cartesian.h"
+
+#include "ardour/types.h"
+#include "ardour/automation_control.h"
+#include "ardour/automatable.h"
+
+namespace ARDOUR {
+
+class PannerStereoBase : public class Panner
+{
+  public:
+       PannerStereoBase (Panner&);
+       ~PannerStereoBase ();
+
+        void set_position (double);
+
+        ChanCount in() const { return ChanCount (DataType::AUDIO, 1); }
+        ChanCount out() const { return ChanCount (DataType::AUDIO, 2); }
+
+       /* this class just leaves the pan law itself to be defined
+          by the update(), do_distribute_automated()
+          methods. derived classes also need a factory method
+          and a type name. See EqualPowerStereoPanner as an example.
+       */
+
+       void do_distribute (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes);
+
+  protected:
+        boost::shared_ptr<AutomationControl> _position;
+       float left;
+       float right;
+       float desired_left;
+       float desired_right;
+       float left_interp;
+       float right_interp;
+};
+
+}
+
+#endif /* __ardour_panner_1in2out_h__ */
diff --git a/libs/panners/2in2out/panner_2in2out.cc b/libs/panners/2in2out/panner_2in2out.cc
new file mode 100644 (file)
index 0000000..6bc0f93
--- /dev/null
@@ -0,0 +1,461 @@
+/*
+    Copyright (C) 2004-2011 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 <inttypes.h>
+
+#include <cmath>
+#include <cerrno>
+#include <fstream>
+#include <cstdlib>
+#include <string>
+#include <cstdio>
+#include <locale.h>
+#include <unistd.h>
+#include <float.h>
+#include <iomanip>
+
+#include <glibmm.h>
+
+#include "pbd/cartesian.h"
+#include "pbd/convert.h"
+#include "pbd/error.h"
+#include "pbd/failed_constructor.h"
+#include "pbd/xml++.h"
+#include "pbd/enumwriter.h"
+
+#include "evoral/Curve.hpp"
+
+#include "ardour/audio_buffer.h"
+#include "ardour/audio_buffer.h"
+#include "ardour/buffer_set.h"
+#include "ardour/pan_controllable.h"
+#include "ardour/pannable.h"
+#include "ardour/runtime_functions.h"
+#include "ardour/session.h"
+#include "ardour/utils.h"
+
+#include "panner_2in2out.h"
+
+#include "i18n.h"
+
+#include "pbd/mathfix.h"
+
+using namespace std;
+using namespace ARDOUR;
+using namespace PBD;
+
+static PanPluginDescriptor _descriptor = {
+        "Equal Power Stereo",
+        2, 2,
+        Panner2in2out::factory
+};
+
+extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
+
+Panner2in2out::Panner2in2out (boost::shared_ptr<Pannable> p)
+       : Panner (p)
+{
+        _pannable->pan_azimuth_control->set_value (0.5);
+        _pannable->pan_width_control->set_value (1.0);
+
+        /* LEFT SIGNAL, panned hard left */
+        left[0] = 1.0;
+        right[0] = 0.0;
+        desired_left[0] = left_interp[0] = left[0];
+        desired_right[0] = right_interp[0] = right[0];
+
+        /* RIGHT SIGNAL, panned hard right */
+        left[1] = 0;
+        right[1] = 1.0;
+        desired_left[1] = left_interp[1] = left[1];
+        desired_right[1] = right_interp[1] = right[1];
+
+        _pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
+        _pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
+}
+
+Panner2in2out::~Panner2in2out ()
+{
+}
+
+double 
+Panner2in2out::position () const
+{
+        return _pannable->pan_azimuth_control->get_value();
+}
+
+double 
+Panner2in2out::width () const
+{
+        return _pannable->pan_width_control->get_value();
+}
+
+void
+Panner2in2out::set_position (double p)
+{
+        if (clamp_position (p)) {
+                _pannable->pan_azimuth_control->set_value (p);
+        }
+}
+
+void
+Panner2in2out::set_width (double p)
+{
+        if (clamp_width (p)) {
+                _pannable->pan_width_control->set_value (p);
+        }
+}
+
+void
+Panner2in2out::update ()
+{
+        /* it would be very nice to split this out into a virtual function
+           that can be accessed from BaseStereoPanner and used in do_distribute_automated().
+           
+           but the place where its used in do_distribute_automated() is a tight inner loop,
+           and making "nframes" virtual function calls to compute values is an absurd
+           overhead.
+        */
+        
+        /* x == 0 => hard left = 180.0 degrees
+           x == 1 => hard right = 0.0 degrees
+        */
+        
+        float pos[2];
+        const double width = _pannable->pan_width_control->get_value();
+        const double direction_as_lr_fract = _pannable->pan_azimuth_control->get_value();
+
+        cerr << "new pan values width=" << width << " LR = " << direction_as_lr_fract << endl;
+
+        if (width < 0.0) {
+                pos[0] = direction_as_lr_fract + (width/2.0); // left signal lr_fract
+                pos[1] = direction_as_lr_fract - (width/2.0); // right signal lr_fract
+        } else {
+                pos[1] = direction_as_lr_fract + (width/2.0); // right signal lr_fract
+                pos[0] = direction_as_lr_fract - (width/2.0); // left signal lr_fract
+        }
+        
+        /* compute target gain coefficients for both input signals */
+        
+        float const pan_law_attenuation = -3.0f;
+        float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
+        float panR;
+        float panL;
+        
+        /* left signal */
+        
+        panR = pos[0];
+        panL = 1 - panR;
+        desired_left[0] = panL * (scale * panL + 1.0f - scale);
+        desired_right[0] = panR * (scale * panR + 1.0f - scale);
+        
+        /* right signal */
+        
+        panR = pos[1];
+        panL = 1 - panR;
+        desired_left[1] = panL * (scale * panL + 1.0f - scale);
+        desired_right[1] = panR * (scale * panR + 1.0f - scale);
+}
+
+bool
+Panner2in2out::clamp_position (double& p)
+{
+        double w = _pannable->pan_width_control->get_value();
+        return clamp_stereo_pan (p, w);
+}
+
+bool
+Panner2in2out::clamp_width (double& w)
+{
+        double p = _pannable->pan_azimuth_control->get_value();
+        return clamp_stereo_pan (p, w);
+}
+
+bool
+Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
+{
+        double r_pos = direction_as_lr_fract + (width/2.0);
+        double l_pos = direction_as_lr_fract - (width/2.0);
+        bool can_move_left = true;
+        bool can_move_right = true;
+
+        cerr << "Clamp pos = " << direction_as_lr_fract << " w = " << width << endl;
+
+        if (width > 1.0 || width < 1.0) {
+                return false;
+        }
+
+        if (direction_as_lr_fract > 1.0 || direction_as_lr_fract < 0.0) {
+                return false;
+        }
+
+        if (width < 0.0) {
+                swap (r_pos, l_pos);
+        }
+
+        /* if the new left position is less than or equal to zero (hard left) and the left panner
+           is already there, we're not moving the left signal. 
+        */
+        
+        if (l_pos <= 0.0 && desired_left[0] <= 0.0) {
+                can_move_left = false;
+        }
+
+        /* if the new right position is less than or equal to 1.0 (hard right) and the right panner
+           is already there, we're not moving the right signal. 
+        */
+        
+        if (r_pos >= 1.0 && desired_right[1] >= 1.0) {
+                can_move_right = false;
+        }
+
+        return can_move_left && can_move_right;
+}
+
+void
+Panner2in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which)
+{
+       assert (obufs.count().n_audio() == 2);
+
+       pan_t delta;
+       Sample* dst;
+       pan_t pan;
+
+       Sample* const src = srcbuf.data();
+        
+       /* LEFT OUTPUT */
+
+       dst = obufs.get_audio(0).data();
+
+       if (fabsf ((delta = (left[which] - desired_left[which]))) > 0.002) { // about 1 degree of arc
+
+               /* we've moving the pan by an appreciable amount, so we must
+                  interpolate over 64 frames or nframes, whichever is smaller */
+
+               pframes_t const limit = min ((pframes_t) 64, nframes);
+               pframes_t n;
+
+               delta = -(delta / (float) (limit));
+
+               for (n = 0; n < limit; n++) {
+                       left_interp[which] = left_interp[which] + delta;
+                       left[which] = left_interp[which] + 0.9 * (left[which] - left_interp[which]);
+                       dst[n] += src[n] * left[which] * gain_coeff;
+               }
+
+               /* then pan the rest of the buffer; no need for interpolation for this bit */
+
+               pan = left[which] * gain_coeff;
+
+               mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
+
+       } else {
+
+               left[which] = desired_left[which];
+               left_interp[which] = left[which];
+
+               if ((pan = (left[which] * gain_coeff)) != 1.0f) {
+
+                       if (pan != 0.0f) {
+
+                               /* pan is 1 but also not 0, so we must do it "properly" */
+
+                               mix_buffers_with_gain(dst,src,nframes,pan);
+
+                               /* mark that we wrote into the buffer */
+
+                               // obufs[0] = 0;
+
+                       }
+
+               } else {
+
+                       /* pan is 1 so we can just copy the input samples straight in */
+
+                       mix_buffers_no_gain(dst,src,nframes);
+                        
+                       /* XXX it would be nice to mark that we wrote into the buffer */
+               }
+       }
+
+       /* RIGHT OUTPUT */
+
+       dst = obufs.get_audio(1).data();
+
+       if (fabsf ((delta = (right[which] - desired_right[which]))) > 0.002) { // about 1 degree of arc
+
+               /* we're moving the pan by an appreciable amount, so we must
+                  interpolate over 64 frames or nframes, whichever is smaller */
+
+               pframes_t const limit = min ((pframes_t) 64, nframes);
+               pframes_t n;
+
+               delta = -(delta / (float) (limit));
+
+               for (n = 0; n < limit; n++) {
+                       right_interp[which] = right_interp[which] + delta;
+                       right[which] = right_interp[which] + 0.9 * (right[which] - right_interp[which]);
+                       dst[n] += src[n] * right[which] * gain_coeff;
+               }
+
+               /* then pan the rest of the buffer, no need for interpolation for this bit */
+
+               pan = right[which] * gain_coeff;
+
+               mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
+
+               /* XXX it would be nice to mark the buffer as written to */
+
+       } else {
+
+               right[which] = desired_right[which];
+               right_interp[which] = right[which];
+
+               if ((pan = (right[which] * gain_coeff)) != 1.0f) {
+
+                       if (pan != 0.0f) {
+
+                               /* pan is not 1 but also not 0, so we must do it "properly" */
+                               
+                               mix_buffers_with_gain(dst,src,nframes,pan);
+
+                               /* XXX it would be nice to mark the buffer as written to */
+                       }
+
+               } else {
+
+                       /* pan is 1 so we can just copy the input samples straight in */
+                       
+                       mix_buffers_no_gain(dst,src,nframes);
+
+                       /* XXX it would be nice to mark the buffer as written to */
+               }
+       }
+}
+
+void
+Panner2in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
+                                         framepos_t start, framepos_t end, pframes_t nframes,
+                                         pan_t** buffers, uint32_t which)
+{
+       assert (obufs.count().n_audio() == 2);
+
+       Sample* dst;
+       pan_t* pbuf;
+       Sample* const src = srcbuf.data();
+        pan_t* const position = buffers[0];
+        pan_t* const width = buffers[1];
+
+       /* fetch positional data */
+
+       if (!_pannable->pan_azimuth_control->list()->curve().rt_safe_get_vector (start, end, position, nframes)) {
+               /* fallback */
+                distribute_one (srcbuf, obufs, 1.0, nframes, which);
+               return;
+       }
+
+       if (!_pannable->pan_width_control->list()->curve().rt_safe_get_vector (start, end, width, nframes)) {
+               /* fallback */
+                distribute_one (srcbuf, obufs, 1.0, nframes, which);
+               return;
+       }
+
+       /* apply pan law to convert positional data into pan coefficients for
+          each buffer (output)
+       */
+
+       const float pan_law_attenuation = -3.0f;
+       const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
+
+       for (pframes_t n = 0; n < nframes; ++n) {
+
+                float panR;
+
+                if (which == 0) { 
+                        // panning left signal
+                        panR = position[n] - (width[n]/2.0f); // center - width/2
+                } else {
+                        // panning right signal
+                        panR = position[n] + (width[n]/2.0f); // center - width/2
+                }
+
+                const float panL = 1 - panR;
+
+                /* note that are overwriting buffers, but its OK
+                   because we're finished with their old contents
+                   (position/width automation data) and are
+                   replacing it with panning/gain coefficients 
+                   that we need to actually process the data.
+                */
+                
+                buffers[0][n] = panL * (scale * panL + 1.0f - scale);
+                buffers[1][n] = panR * (scale * panR + 1.0f - scale);
+        }
+
+       /* LEFT OUTPUT */
+
+       dst = obufs.get_audio(0).data();
+       pbuf = buffers[0];
+
+       for (pframes_t n = 0; n < nframes; ++n) {
+               dst[n] += src[n] * pbuf[n];
+       }
+
+       /* XXX it would be nice to mark the buffer as written to */
+
+       /* RIGHT OUTPUT */
+
+       dst = obufs.get_audio(1).data();
+       pbuf = buffers[1];
+
+       for (pframes_t n = 0; n < nframes; ++n) {
+               dst[n] += src[n] * pbuf[n];
+       }
+
+       /* XXX it would be nice to mark the buffer as written to */
+}
+
+Panner*
+Panner2in2out::factory (boost::shared_ptr<Pannable> p, Speakers& /* ignored */)
+{
+       return new Panner2in2out (p);
+}
+
+XMLNode&
+Panner2in2out::get_state (void)
+{
+       return state (true);
+}
+
+XMLNode&
+Panner2in2out::state (bool /*full_state*/)
+{
+       XMLNode& root (Panner::get_state ());
+       root.add_property (X_("type"), _descriptor.name);
+       return root;
+}
+
+int
+Panner2in2out::set_state (const XMLNode& node, int version)
+{
+       LocaleGuard lg (X_("POSIX"));
+       Panner::set_state (node, version);
+       return 0;
+}
+
diff --git a/libs/panners/2in2out/panner_2in2out.h b/libs/panners/2in2out/panner_2in2out.h
new file mode 100644 (file)
index 0000000..8d8d57d
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+    Copyright (C) 2004-2011 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 __ardour_panner_2in2out_h__
+#define __ardour_panner_2in2out_h__
+
+#include <cmath>
+#include <cassert>
+#include <vector>
+#include <string>
+#include <iostream>
+
+#include "pbd/stateful.h"
+#include "pbd/controllable.h"
+#include "pbd/cartesian.h"
+
+#include "ardour/automation_control.h"
+#include "ardour/automatable.h"
+#include "ardour/panner.h"
+#include "ardour/types.h"
+
+namespace ARDOUR {
+
+class Panner2in2out : public Panner
+{
+  public:
+       Panner2in2out (boost::shared_ptr<Pannable>);
+       ~Panner2in2out ();
+
+        ChanCount in() const { return ChanCount (DataType::AUDIO, 2); }
+        ChanCount out() const { return ChanCount (DataType::AUDIO, 2); }
+
+        bool clamp_position (double&);
+        bool clamp_width (double&);
+
+        void set_position (double);
+        void set_width (double);
+
+        double position () const;
+        double width () const;
+
+       static Panner* factory (boost::shared_ptr<Pannable>, Speakers&);
+
+       XMLNode& state (bool full_state); 
+       XMLNode& get_state (void); 
+       int      set_state (const XMLNode&, int version);
+
+        void update ();
+
+  protected:
+       float left[2];
+       float right[2];
+       float desired_left[2];
+       float desired_right[2];
+       float left_interp[2];
+       float right_interp[2];
+
+  private:
+        bool clamp_stereo_pan (double& direction_as_lr_fract, double& width);
+        
+        void distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which);
+        void distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
+                                       framepos_t start, framepos_t end, pframes_t nframes,
+                                       pan_t** buffers, uint32_t which);
+};
+
+} // namespace
+
+#endif /* __ardour_panner_2in2out_h__ */
diff --git a/libs/panners/2in2out/wscript b/libs/panners/2in2out/wscript
new file mode 100644 (file)
index 0000000..509848e
--- /dev/null
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+import autowaf
+import os
+
+# Library version (UNIX style major, minor, micro)
+# major increment <=> incompatible changes
+# minor increment <=> compatible changes (additions)
+# micro increment <=> no interface changes
+LIBARDOUR_PAN2IN2OUT_LIB_VERSION = '1.0.0'
+
+# Mandatory variables
+srcdir = '.'
+blddir = 'build'
+
+def build(bld):
+       obj = bld.new_task_gen('cxx', 'shlib')
+       obj.source = [ 'panner_2in2out.cc' ]
+       obj.export_incdirs = ['.']
+       obj.cxxflags     = '-DPACKAGE="libardour_pan2in2out"'
+       obj.includes     = ['.']
+       obj.name         = 'libardour_pan2in2out'
+       obj.target       = 'pan2in2out'
+       obj.uselib_local = 'libardour libardour_cp libpbd'
+       obj.vnum         = LIBARDOUR_PAN2IN2OUT_LIB_VERSION
+       obj.install_path = os.path.join(bld.env['LIBDIR'], 'ardour3', 'panners')
+
+def shutdown():
+       autowaf.shutdown()
+
diff --git a/libs/panners/vbap/vbap.cc b/libs/panners/vbap/vbap.cc
new file mode 100644 (file)
index 0000000..1876f4c
--- /dev/null
@@ -0,0 +1,306 @@
+#include <cmath>
+#include <cstdlib>
+#include <cstdio>
+#include <cstring>
+
+#include <iostream>
+#include <string>
+
+#include "pbd/cartesian.h"
+
+#include "ardour/pannable.h"
+#include "ardour/speakers.h"
+#include "ardour/vbap.h"
+#include "ardour/vbap_speakers.h"
+#include "ardour/audio_buffer.h"
+#include "ardour/buffer_set.h"
+#include "ardour/pan_controllable.h"
+
+using namespace PBD;
+using namespace ARDOUR;
+using namespace std;
+
+static PanPluginDescriptor _descriptor = {
+        "VBAP 2D panner",
+        1, -1, 2, -1,
+        VBAPanner::factory
+};
+
+extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
+
+VBAPanner::Signal::Signal (Session& session, VBAPanner& p, uint32_t n)
+        : azimuth_control (new PanControllable (session, string_compose (_("azimuth %1"), n+1), &p, Evoral::Parameter (PanAzimuthAutomation, 0, n)))
+        , elevation_control (new PanControllable (session, string_compose (_("elevation %1"), n+1), &p, Evoral::Parameter (PanElevationAutomation, 0, n)))
+{
+        gains[0] = gains[1] = gains[2] = 0;
+        desired_gains[0] = desired_gains[1] = desired_gains[2] = 0;
+        outputs[0] = outputs[1] = outputs[2] = -1;
+        desired_outputs[0] = desired_outputs[1] = desired_outputs[2] = -1;
+};
+
+VBAPanner::VBAPanner (boost::shared_ptr<Pannable> p, Speakers& s)
+       : Panner (p)
+       , _dirty (true)
+       , _speakers (VBAPSpeakers::instance (s))
+{
+}
+
+VBAPanner::~VBAPanner ()
+{
+        for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
+                delete *i;
+        }
+}
+
+void
+VBAPanner::configure_io (const ChanCount& in, const ChanCount& /* ignored - we use Speakers */)
+{
+        uint32_t n = in.n_audio();
+
+        /* 2d panning: spread signals equally around a circle */
+        
+        double degree_step = 360.0 / _speakers.n_speakers();
+        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 (n % 2) {
+                deg = 90.0 - degree_step;
+        } else {
+                deg = 90.0;
+        }
+
+        _signals.clear ();
+        
+        for (uint32_t i = 0; i < n; ++i) {
+                _signals.push_back (new Signal (_pannable->session(), *this, i));
+                _signals[i]->direction = AngularVector (deg, 0.0);
+                deg += degree_step;
+        }
+}
+
+void 
+VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele) 
+{
+       /* calculates gain factors using loudspeaker setup and given direction */
+       double cartdir[3];
+       double power;
+       int i,j,k;
+       double small_g;
+       double big_sm_g, gtmp[3];
+
+       azi_ele_to_cart (azi,ele, cartdir[0], cartdir[1], cartdir[2]);  
+       big_sm_g = -100000.0;
+
+       gains[0] = gains[1] = gains[2] = 0;
+       speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
+
+       for (i = 0; i < _speakers.n_tuples(); i++) {
+
+               small_g = 10000000.0;
+
+               for (j = 0; j < _speakers.dimension(); j++) {
+
+                       gtmp[j] = 0.0;
+
+                       for (k = 0; k < _speakers.dimension(); k++) {
+                               gtmp[j] += cartdir[k] * _speakers.matrix(i)[j*_speakers.dimension()+k]; 
+                       }
+
+                       if (gtmp[j] < small_g) {
+                               small_g = gtmp[j];
+                       }
+               }
+
+               if (small_g > big_sm_g) {
+
+                       big_sm_g = small_g;
+
+                       gains[0] = gtmp[0]; 
+                       gains[1] = gtmp[1]; 
+
+                       speaker_ids[0] = _speakers.speaker_for_tuple (i, 0);
+                       speaker_ids[1] = _speakers.speaker_for_tuple (i, 1);
+                        
+                       if (_speakers.dimension() == 3) {
+                               gains[2] = gtmp[2];
+                               speaker_ids[2] = _speakers.speaker_for_tuple (i, 2);
+                       } else {
+                               gains[2] = 0.0;
+                               speaker_ids[2] = -1;
+                       }
+               }
+       }
+        
+       power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
+
+       if (power > 0) {
+               gains[0] /= power; 
+               gains[1] /= power;
+               gains[2] /= power;
+       }
+
+       _dirty = false;
+}
+
+void
+VBAPanner::do_distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
+{
+       bool was_dirty = _dirty;
+        uint32_t n;
+        vector<Signal*>::iterator s;
+
+        assert (inbufs.count().n_audio() == _signals.size());
+
+        /* XXX need to handle mono case */
+
+        for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
+
+                Signal* signal (*s);
+
+                if (was_dirty) {
+                        compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
+                        cerr << " @ " << signal->direction.azi << " /= " << signal->direction.ele
+                             << " Outputs: "
+                             << signal->desired_outputs[0] + 1 << ' '
+                             << signal->desired_outputs[1] + 1 << ' '
+                             << " Gains "
+                             << signal->desired_gains[0] << ' '
+                             << signal->desired_gains[1] << ' '
+                             << endl;
+                }
+                
+                do_distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
+
+                if (was_dirty) {
+                        memcpy (signal->gains, signal->desired_gains, sizeof (signal->gains));
+                        memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
+                }
+        }
+}
+
+
+void
+VBAPanner::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
+{
+       Sample* const src = srcbuf.data();
+       Sample* dst;
+       pan_t pan;
+       uint32_t n_audio = obufs.count().n_audio();
+       bool todo[n_audio];
+        Signal* signal (_signals[which]);
+
+       for (uint32_t o = 0; o < n_audio; ++o) {
+               todo[o] = true;
+       }
+        
+       /* VBAP may distribute the signal across up to 3 speakers depending on
+          the configuration of the speakers.
+       */
+
+       for (int o = 0; o < 3; ++o) {
+               if (signal->desired_outputs[o] != -1) {
+                        
+                       pframes_t n = 0;
+
+                       /* XXX TODO: interpolate across changes in gain and/or outputs
+                        */
+
+                       dst = obufs.get_audio (signal->desired_outputs[o]).data();
+
+                       pan = gain_coefficient * signal->desired_gains[o];
+                       mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
+
+                       todo[o] = false;
+               }
+       }
+        
+       for (uint32_t o = 0; o < n_audio; ++o) {
+               if (todo[o]) {
+                       /* VBAP decided not to deliver any audio to this output, so we write silence */
+                       dst = obufs.get_audio(o).data();
+                       memset (dst, 0, sizeof (Sample) * nframes);
+               }
+       }
+        
+}
+
+void 
+VBAPanner::do_distribute_one_automated (AudioBuffer& src, BufferSet& obufs,
+                                        framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers, uint32_t which)
+{
+}
+
+XMLNode&
+VBAPanner::get_state ()
+{
+       return state (true);
+}
+
+XMLNode&
+VBAPanner::state (bool full_state)
+{
+        XMLNode& node (Panner::get_state());
+       node.add_property (X_("type"), _descriptor.name);
+       return node;
+}
+
+int
+VBAPanner::set_state (const XMLNode& node, int /*version*/)
+{
+       return 0;
+}
+
+boost::shared_ptr<AutomationControl>
+VBAPanner::azimuth_control (uint32_t n)
+{
+        if (n >= _signals.size()) {
+                return boost::shared_ptr<AutomationControl>();
+        }
+        return _signals[n]->azimuth_control;
+}
+
+boost::shared_ptr<AutomationControl>
+VBAPanner::evelation_control (uint32_t n)
+{
+        if (n >= _signals.size()) {
+                return boost::shared_ptr<AutomationControl>();
+        }
+        return _signals[n]->elevation_control;
+}
+
+Panner*
+VBAPanner::factory (boost::shared_ptr<Pannable> p, Speakers& s)
+{
+       return new VBAPanner (p, s);
+}
+
+string
+VBAPanner::describe_parameter (Evoral::Parameter param)
+{
+        stringstream ss;
+        switch (param.type()) {
+        case PanElevationAutomation:
+                return string_compose ( _("Pan:elevation %1"), param.id() + 1);
+        case PanWidthAutomation:
+                return string_compose ( _("Pan:diffusion %1"), param.id() + 1);
+        case PanAzimuthAutomation:
+                return string_compose ( _("Pan:azimuth %1"), param.id() + 1);
+        }
+
+        return Automatable::describe_parameter (param);
+}
+
+ChanCount
+VBAPanner::in() const
+{
+        return ChanCount (DataType::AUDIO, _signals.size());
+}
+
+ChanCount
+VBAPanner::out() const
+{
+        return ChanCount (DataType::AUDIO, _speakers.n_speakers());
+}
diff --git a/libs/panners/vbap/vbap.h b/libs/panners/vbap/vbap.h
new file mode 100644 (file)
index 0000000..aacff88
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+    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_vbap_h__
+#define __libardour_vbap_h__
+
+#include <string>
+#include <map>
+
+#include "pbd/cartesian.h"
+
+#include "ardour/panner.h"
+#include "ardour/panner_shell.h"
+#include "ardour/vbap_speakers.h"
+
+namespace ARDOUR {
+
+class Speakers;
+class Pannable;
+
+class VBAPanner : public Panner 
+{ 
+public:
+       VBAPanner (boost::shared_ptr<Pannable>, Speakers& s);
+       ~VBAPanner ();
+
+        void configure_io (const ChanCount& in, const ChanCount& /* ignored - we use Speakers */);
+        ChanCount in() const;
+        ChanCount out() const;
+
+       static Panner* factory (boost::shared_ptr<Pannable>, Speakers& s);
+
+       void do_distribute (BufferSet& ibufs, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes);
+       void do_distribute_automated (BufferSet& ibufs, BufferSet& obufs,
+                                     framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers);
+
+       void set_azimuth_elevation (double azimuth, double elevation);
+
+       XMLNode& state (bool full_state);
+       XMLNode& get_state ();
+       int set_state (const XMLNode&, int version);
+
+        boost::shared_ptr<AutomationControl> azimuth_control (uint32_t signal);
+        boost::shared_ptr<AutomationControl> evelation_control (uint32_t signal);
+
+        std::string describe_parameter (Evoral::Parameter param);
+
+private:
+        struct Signal {
+            PBD::AngularVector direction;
+            double gains[3];
+            double desired_gains[3];
+            int    outputs[3];
+            int    desired_outputs[3];
+            boost::shared_ptr<AutomationControl> azimuth_control;
+            boost::shared_ptr<AutomationControl> elevation_control;
+
+            Signal (Session&, VBAPanner&, uint32_t which);
+        };
+
+        std::vector<Signal*> _signals;
+       bool                _dirty;
+        VBAPSpeakers&       _speakers;
+        
+       void compute_gains (double g[3], int ls[3], int azi, int ele);
+
+       void do_distribute_one (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which);
+       void do_distribute_one_automated (AudioBuffer& src, BufferSet& obufs,
+                                          framepos_t start, framepos_t end, pframes_t nframes, 
+                                          pan_t** buffers, uint32_t which);
+};
+
+} /* namespace */
+
+#endif /* __libardour_vbap_h__ */
diff --git a/libs/panners/vbap/vbap_speakers.cc b/libs/panners/vbap/vbap_speakers.cc
new file mode 100644 (file)
index 0000000..9090ed6
--- /dev/null
@@ -0,0 +1,658 @@
+/* 
+   This software is being provided to you, the licensee, by Ville Pulkki,
+   under the following license. By obtaining, using and/or copying this
+   software, you agree that you have read, understood, and will comply
+   with these terms and conditions: Permission to use, copy, modify and
+   distribute, including the right to grant others rights to distribute
+   at any tier, this software and its documentation for any purpose and
+   without fee or royalty is hereby granted, provided that you agree to
+   comply with the following copyright notice and statements, including
+   the disclaimer, and that the same appear on ALL copies of the software
+   and documentation, including modifications that you make for internal
+   use or for distribution:
+   
+   Copyright 1998 by Ville Pulkki, Helsinki University of Technology.  All
+   rights reserved.  
+   
+   The software may be used, distributed, and included to commercial
+   products without any charges. When included to a commercial product,
+   the method "Vector Base Amplitude Panning" and its developer Ville
+   Pulkki must be referred to in documentation.
+   
+   This software is provided "as is", and Ville Pulkki or Helsinki
+   University of Technology make no representations or warranties,
+   expressed or implied. By way of example, but not limitation, Helsinki
+   University of Technology or Ville Pulkki make no representations or
+   warranties of merchantability or fitness for any particular purpose or
+   that the use of the licensed software or documentation will not
+   infringe any third party patents, copyrights, trademarks or other
+   rights. The name of Ville Pulkki or Helsinki University of Technology
+   may not be used in advertising or publicity pertaining to distribution
+   of the software.
+*/
+
+#include <cmath>
+#include <algorithm>
+#include <stdlib.h>
+
+#include "pbd/cartesian.h"
+#include "ardour/vbap_speakers.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+using namespace std;
+
+VBAPSpeakers* VBAPSpeakers::_instance = 0;
+
+VBAPSpeakers& 
+VBAPSpeakers::instance (Speakers& s)
+{
+       if (_instance == 0) {
+               _instance = new VBAPSpeakers (s);
+       }
+
+       return *_instance;
+}
+
+VBAPSpeakers::VBAPSpeakers (Speakers& s)
+       : _dimension (2)
+       , _speakers (s.speakers())
+{
+       s.Changed.connect_same_thread (speaker_connection, boost::bind (&VBAPSpeakers::update, this));
+}
+
+VBAPSpeakers::~VBAPSpeakers ()
+{
+}
+
+void
+VBAPSpeakers::update ()
+{
+       int dim = 2;
+        
+       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;
+               }
+       }
+
+       _dimension = dim;
+
+       cerr << "update with dimension = " << dim << " speakers = " << _speakers.size() << endl;
+
+       if (_speakers.size() < 2) {
+               /* nothing to be done with less than two speakers */
+               return;
+       }
+
+       if (_dimension == 3)  {
+               ls_triplet_chain *ls_triplets = 0;
+               choose_speaker_triplets (&ls_triplets);
+               if (ls_triplets) {
+                       calculate_3x3_matrixes (ls_triplets);
+                       free (ls_triplets);
+               }
+       } else {
+               choose_speaker_pairs ();
+       }
+}
+
+void 
+VBAPSpeakers::choose_speaker_triplets(struct ls_triplet_chain **ls_triplets) 
+{
+       /* Selects the loudspeaker triplets, and
+          calculates the inversion matrices for each selected triplet.
+          A line (connection) is drawn between each loudspeaker. The lines
+          denote the sides of the triangles. The triangles should not be 
+          intersecting. All crossing connections are searched and the 
+          longer connection is erased. This yields non-intesecting triangles,
+          which can be used in panning.
+       */
+
+       int i,j,k,l,table_size;
+       int n_speakers = _speakers.size ();
+       int connections[n_speakers][n_speakers];
+       float distance_table[((n_speakers * (n_speakers - 1)) / 2)];
+       int distance_table_i[((n_speakers * (n_speakers - 1)) / 2)];
+       int distance_table_j[((n_speakers * (n_speakers - 1)) / 2)];
+       float distance;
+       struct ls_triplet_chain *trip_ptr, *prev, *tmp_ptr;
+
+       if (n_speakers == 0) {
+               return;
+       }
+
+       for (i = 0; i < n_speakers; i++) {
+               for (j = i+1; j < n_speakers; j++) {
+                       for(k=j+1;k<n_speakers;k++) {
+                               if (vol_p_side_lgth(i,j, k, _speakers) > MIN_VOL_P_SIDE_LGTH){
+                                       connections[i][j]=1;
+                                       connections[j][i]=1;
+                                       connections[i][k]=1;
+                                       connections[k][i]=1;
+                                       connections[j][k]=1;
+                                       connections[k][j]=1;
+                                       add_ldsp_triplet(i,j,k,ls_triplets);
+                               }
+                       }
+               }
+       }
+
+       /*calculate distancies between all speakers and sorting them*/
+       table_size =(((n_speakers - 1) * (n_speakers)) / 2); 
+       for (i = 0; i < table_size; i++) {
+               distance_table[i] = 100000.0;
+       }
+
+       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()));
+                               k=0;
+                               while(distance_table[k] < distance) {
+                                       k++;
+                               }
+                               for (l = table_size - 1; l > k ; l--) {
+                                       distance_table[l] = distance_table[l-1];
+                                       distance_table_i[l] = distance_table_i[l-1];
+                                       distance_table_j[l] = distance_table_j[l-1];
+                               }
+                               distance_table[k] = distance;
+                               distance_table_i[k] = i;
+                               distance_table_j[k] = j;
+                       } else
+                               table_size--;
+               }
+       }
+
+       /* disconnecting connections which are crossing shorter ones,
+          starting from shortest one and removing all that cross it,
+          and proceeding to next shortest */
+       for (i = 0; i < table_size; i++) {
+               int fst_ls = distance_table_i[i];
+               int sec_ls = distance_table_j[i];
+               if (connections[fst_ls][sec_ls] == 1) {
+                       for (j = 0; j < n_speakers; j++) {
+                               for (k = j+1; k < n_speakers; k++) {
+                                       if ((j!=fst_ls) && (k != sec_ls) && (k!=fst_ls) && (j != sec_ls)){
+                                               if (lines_intersect(fst_ls, sec_ls, j,k) == 1){
+                                                       connections[j][k] = 0;
+                                                       connections[k][j] = 0;
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       /* remove triangles which had crossing sides
+          with smaller triangles or include loudspeakers*/
+       trip_ptr = *ls_triplets;
+       prev = 0;
+       while (trip_ptr != 0){
+               i = trip_ptr->ls_nos[0];
+               j = trip_ptr->ls_nos[1];
+               k = trip_ptr->ls_nos[2];
+               if (connections[i][j] == 0 || 
+                   connections[i][k] == 0 || 
+                   connections[j][k] == 0 ||
+                   any_ls_inside_triplet(i,j,k) == 1 ){
+                       if (prev != 0) {
+                               prev->next = trip_ptr->next;
+                               tmp_ptr = trip_ptr;
+                               trip_ptr = trip_ptr->next;
+                               free(tmp_ptr);
+                       } else {
+                               *ls_triplets = trip_ptr->next;
+                               tmp_ptr = trip_ptr;
+                               trip_ptr = trip_ptr->next;
+                               free(tmp_ptr);
+                       }
+               } else {
+                       prev = trip_ptr;
+                       trip_ptr = trip_ptr->next;
+
+               }
+       }
+}
+
+int 
+VBAPSpeakers::any_ls_inside_triplet(int a, int b, int c)
+{
+       /* returns 1 if there is loudspeaker(s) inside given ls triplet */
+       float invdet;
+       const CartesianVector* lp1;
+       const CartesianVector* lp2;
+       const CartesianVector* lp3;
+       float invmx[9];
+       int i,j;
+       float tmp;
+       bool any_ls_inside;
+       bool this_inside;
+       int n_speakers = _speakers.size();
+
+       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))
+                         - lp1->y * ((lp2->x * lp3->z) - (lp2->z * lp3->x))
+                         + lp1->z * ((lp2->x * lp3->y) - (lp2->y * lp3->x)));
+        
+       invmx[0] = ((lp2->y * lp3->z) - (lp2->z * lp3->y)) * invdet;
+       invmx[3] = ((lp1->y * lp3->z) - (lp1->z * lp3->y)) * -invdet;
+       invmx[6] = ((lp1->y * lp2->z) - (lp1->z * lp2->y)) * invdet;
+       invmx[1] = ((lp2->x * lp3->z) - (lp2->z * lp3->x)) * -invdet;
+       invmx[4] = ((lp1->x * lp3->z) - (lp1->z * lp3->x)) * invdet;
+       invmx[7] = ((lp1->x * lp2->z) - (lp1->z * lp2->x)) * -invdet;
+       invmx[2] = ((lp2->x * lp3->y) - (lp2->y * lp3->x)) * invdet;
+       invmx[5] = ((lp1->x * lp3->y) - (lp1->y * lp3->x)) * -invdet;
+       invmx[8] = ((lp1->x * lp2->y) - (lp1->y * lp2->x)) * invdet;
+        
+       any_ls_inside = false;
+       for (i = 0; i < n_speakers; i++) {
+               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];
+                               if (tmp < -0.001) {
+                                       this_inside = false;
+                               }
+                       }
+                       if (this_inside) {
+                               any_ls_inside = true;
+                       }
+               }
+       }
+
+       return any_ls_inside;
+}
+
+
+void 
+VBAPSpeakers::add_ldsp_triplet(int i, int j, int k, struct ls_triplet_chain **ls_triplets)
+{
+       /* adds i,j,k triplet to triplet chain*/
+
+       struct ls_triplet_chain *trip_ptr, *prev;
+       trip_ptr = *ls_triplets;
+       prev = 0;
+        
+       while (trip_ptr != 0){
+               prev = trip_ptr;
+               trip_ptr = trip_ptr->next;
+       }
+
+       trip_ptr = (struct ls_triplet_chain*) malloc (sizeof (struct ls_triplet_chain));
+
+       if (prev == 0) {
+               *ls_triplets = trip_ptr;
+       } else {
+               prev->next = trip_ptr;
+       }
+
+       trip_ptr->next = 0;
+       trip_ptr->ls_nos[0] = i;
+       trip_ptr->ls_nos[1] = j;
+       trip_ptr->ls_nos[2] = k;
+}
+
+float 
+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)));
+
+       if (inner > 1.0) {
+               inner= 1.0;
+       }
+
+       if (inner < -1.0) {
+               inner = -1.0;
+       }
+
+       return fabsf((float) acos((double) inner));
+}
+
+float 
+VBAPSpeakers::vec_length(CartesianVector v1)
+{
+       return (sqrt(v1.x*v1.x + v1.y*v1.y + v1.z*v1.z));
+}
+
+float 
+VBAPSpeakers::vec_prod(CartesianVector v1, CartesianVector v2)
+{
+       return (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z);
+}
+
+float 
+VBAPSpeakers::vol_p_side_lgth(int i, int j,int k, const vector<Speaker>& speakers)
+{
+       /* calculate volume of the parallelepiped defined by the loudspeaker
+          direction vectors and divide it with total length of the triangle sides. 
+          This is used when removing too narrow triangles. */
+        
+       float volper, lgth;
+       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())));
+
+       if (lgth > 0.00001) {
+               return volper / lgth;
+       } else {
+               return 0.0;
+       }
+}
+
+void 
+VBAPSpeakers::cross_prod(CartesianVector v1,CartesianVector v2, CartesianVector *res) 
+{
+       float length;
+
+       res->x = (v1.y * v2.z ) - (v1.z * v2.y);
+       res->y = (v1.z * v2.x ) - (v1.x * v2.z);
+       res->z = (v1.x * v2.y ) - (v1.y * v2.x);
+        
+       length = vec_length(*res);
+       res->x /= length;
+       res->y /= length;
+       res->z /= length;
+}
+
+int 
+VBAPSpeakers::lines_intersect (int i, int j, int k, int l)
+{
+       /* checks if two lines intersect on 3D sphere 
+          see theory in paper Pulkki, V. Lokki, T. "Creating Auditory Displays
+          with Multiple Loudspeakers Using VBAP: A Case Study with
+          DIVA Project" in International Conference on 
+          Auditory Displays -98. E-mail Ville.Pulkki@hut.fi
+          if you want to have that paper.
+       */
+
+       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(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()));
+
+       /* if one of loudspeakers is close to crossing point, don't do anything*/
+
+
+       if(fabsf(dist_iv3) <= 0.01 || fabsf(dist_jv3) <= 0.01 || 
+          fabsf(dist_kv3) <= 0.01 || fabsf(dist_lv3) <= 0.01 ||
+          fabsf(dist_inv3) <= 0.01 || fabsf(dist_jnv3) <= 0.01 || 
+          fabsf(dist_knv3) <= 0.01 || fabsf(dist_lnv3) <= 0.01 ) {
+               return(0);
+       }
+
+       if (((fabsf(dist_ij - (dist_iv3 + dist_jv3)) <= 0.01 ) &&
+            (fabsf(dist_kl - (dist_kv3 + dist_lv3))  <= 0.01)) ||
+           ((fabsf(dist_ij - (dist_inv3 + dist_jnv3)) <= 0.01)  &&
+            (fabsf(dist_kl - (dist_knv3 + dist_lnv3)) <= 0.01 ))) {
+               return (1);
+       } else {
+               return (0);
+       }
+}
+
+void  
+VBAPSpeakers::calculate_3x3_matrixes(struct ls_triplet_chain *ls_triplets)
+{  
+       /* Calculates the inverse matrices for 3D */
+       float invdet;
+       const CartesianVector* lp1;
+       const CartesianVector* lp2;
+       const CartesianVector* lp3;
+       float *invmx;
+       struct ls_triplet_chain *tr_ptr = ls_triplets;
+       int triplet_count = 0;
+       int triplet;
+
+       assert (tr_ptr);
+        
+       /* counting triplet amount */
+
+       while (tr_ptr != 0) {
+               triplet_count++;
+               tr_ptr = tr_ptr->next;
+       }
+
+       cerr << "@@@ triplets generate " << triplet_count << " of speaker tuples\n";
+
+       triplet = 0;
+
+       _matrices.clear ();
+       _speaker_tuples.clear ();
+
+       for (int n = 0; n < triplet_count; ++n) {
+               _matrices.push_back (threeDmatrix());
+               _speaker_tuples.push_back (tmatrix());
+       }
+
+       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());
+                
+               /* matrix inversion */
+               invmx = tr_ptr->inv_mx;
+               invdet = 1.0 / (  lp1->x * ((lp2->y * lp3->z) - (lp2->z * lp3->y))
+                                 - lp1->y * ((lp2->x * lp3->z) - (lp2->z * lp3->x))
+                                 + lp1->z * ((lp2->x * lp3->y) - (lp2->y * lp3->x)));
+                
+               invmx[0] = ((lp2->y * lp3->z) - (lp2->z * lp3->y)) * invdet;
+               invmx[3] = ((lp1->y * lp3->z) - (lp1->z * lp3->y)) * -invdet;
+               invmx[6] = ((lp1->y * lp2->z) - (lp1->z * lp2->y)) * invdet;
+               invmx[1] = ((lp2->x * lp3->z) - (lp2->z * lp3->x)) * -invdet;
+               invmx[4] = ((lp1->x * lp3->z) - (lp1->z * lp3->x)) * invdet;
+               invmx[7] = ((lp1->x * lp2->z) - (lp1->z * lp2->x)) * -invdet;
+               invmx[2] = ((lp2->x * lp3->y) - (lp2->y * lp3->x)) * invdet;
+               invmx[5] = ((lp1->x * lp3->y) - (lp1->y * lp3->x)) * -invdet;
+               invmx[8] = ((lp1->x * lp2->y) - (lp1->y * lp2->x)) * invdet;
+                
+               /* copy the matrix */
+
+               _matrices[triplet][0] = invmx[0];
+               _matrices[triplet][1] = invmx[1];
+               _matrices[triplet][2] = invmx[2];
+               _matrices[triplet][3] = invmx[3];
+               _matrices[triplet][4] = invmx[4];
+               _matrices[triplet][5] = invmx[5];
+               _matrices[triplet][6] = invmx[6];
+               _matrices[triplet][7] = invmx[7];
+               _matrices[triplet][8] = invmx[8];
+
+               _speaker_tuples[triplet][0] = tr_ptr->ls_nos[0];
+               _speaker_tuples[triplet][1] = tr_ptr->ls_nos[1];
+               _speaker_tuples[triplet][2] = tr_ptr->ls_nos[2];
+
+               cerr << "Triplet[" << triplet << "] = " 
+                    << tr_ptr->ls_nos[0] << " + " 
+                    << tr_ptr->ls_nos[1] << " + " 
+                    << tr_ptr->ls_nos[2] << endl;
+
+               triplet++;
+
+               tr_ptr = tr_ptr->next;
+       }
+}
+
+void 
+VBAPSpeakers::choose_speaker_pairs (){
+
+       /* selects the loudspeaker pairs, calculates the inversion
+          matrices and stores the data to a global array
+       */
+       const int n_speakers = _speakers.size();
+       const double AZIMUTH_DELTA_THRESHOLD_DEGREES = (180.0/M_PI) * (M_PI - 0.175);
+       int sorted_speakers[n_speakers];
+       bool exists[n_speakers];
+       double inverse_matrix[n_speakers][4]; 
+       int expected_pairs = 0;
+       int pair;
+       int speaker;
+
+       cerr << "CHOOSE PAIRS\n";
+
+       if (n_speakers == 0) {
+               return;
+       }
+
+       for (speaker = 0; speaker < n_speakers; ++speaker) {
+               exists[speaker] = false;
+       }
+
+       /* sort loudspeakers according their aximuth angle */
+       sort_2D_lss (sorted_speakers);
+        
+       /* adjacent loudspeakers are the loudspeaker pairs to be used.*/
+       for (speaker = 0; speaker < n_speakers-1; speaker++) {
+
+               cerr << "Looking at " 
+                    << _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  
+                    << " delta = " 
+                    << _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, 
+                                                inverse_matrix[speaker]) != 0){
+                               exists[speaker] = true;
+                               expected_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, 
+                                       inverse_matrix[n_speakers-1]) != 0) { 
+                       exists[n_speakers-1] = true;
+                       expected_pairs++;
+               } 
+       }
+
+       pair = 0;
+
+       _matrices.clear ();
+       _speaker_tuples.clear ();
+
+       for (int n = 0; n < expected_pairs; ++n) {
+               _matrices.push_back (twoDmatrix());
+               _speaker_tuples.push_back (tmatrix());
+       }
+
+       for (speaker = 0; speaker < n_speakers - 1; speaker++) {
+               if (exists[speaker]) {
+                       _matrices[pair][0] = inverse_matrix[speaker][0];
+                       _matrices[pair][1] = inverse_matrix[speaker][1];
+                       _matrices[pair][2] = inverse_matrix[speaker][2];
+                       _matrices[pair][3] = inverse_matrix[speaker][3];
+
+                       _speaker_tuples[pair][0] = sorted_speakers[speaker];
+                       _speaker_tuples[pair][1] = sorted_speakers[speaker+1];
+
+                       cerr << "PAIR[" << pair << "] = " << sorted_speakers[speaker] << " + " << sorted_speakers[speaker+1] << endl;
+
+                       pair++;
+               }
+       }
+        
+       if (exists[n_speakers-1]) {
+               _matrices[pair][0] = inverse_matrix[speaker][0];
+               _matrices[pair][1] = inverse_matrix[speaker][1];
+               _matrices[pair][2] = inverse_matrix[speaker][2];
+               _matrices[pair][3] = inverse_matrix[speaker][3];
+
+               _speaker_tuples[pair][0] = sorted_speakers[n_speakers-1];
+               _speaker_tuples[pair][1] = sorted_speakers[0];
+
+               cerr << "PAIR[" << pair << "] = " << sorted_speakers[n_speakers-1] << " + " << sorted_speakers[0] << endl;
+
+       }
+}
+
+void 
+VBAPSpeakers::sort_2D_lss (int* sorted_speakers)
+{
+       vector<Speaker> tmp = _speakers;
+       vector<Speaker>::iterator s;
+       azimuth_sorter sorter;
+       int n;
+
+       sort (tmp.begin(), tmp.end(), sorter);
+
+       for (n = 0, s = tmp.begin(); s != tmp.end(); ++s, ++n) {
+               sorted_speakers[n] = (*s).id;
+               cerr << "Sorted[" << n << "] = " << (*s).id << endl;
+       }
+}
+
+int 
+VBAPSpeakers::calc_2D_inv_tmatrix (double azi1, double azi2, double* inverse_matrix)
+{
+       double x1,x2,x3,x4;
+       double det;
+
+       x1 = cos (azi1);
+       x2 = sin (azi1);
+       x3 = cos (azi2);
+       x4 = sin (azi2);
+       det = (x1 * x4) - ( x3 * x2 );
+
+       if (fabs(det) <= 0.001) {
+                
+               inverse_matrix[0] = 0.0;
+               inverse_matrix[1] = 0.0;
+               inverse_matrix[2] = 0.0;
+               inverse_matrix[3] = 0.0;
+
+               return 0;
+
+       } else {
+
+               inverse_matrix[0] = x4 / det;
+               inverse_matrix[1] = -x3 / det;
+               inverse_matrix[2] = -x2 / det;
+               inverse_matrix[3] = x1 / det;
+
+               return 1;
+       }
+}
+
+
diff --git a/libs/panners/vbap/vbap_speakers.h b/libs/panners/vbap/vbap_speakers.h
new file mode 100644 (file)
index 0000000..8fe006e
--- /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.
+*/
+
+#ifndef __libardour_vbap_speakers_h__
+#define __libardour_vbap_speakers_h__
+
+#include <string>
+#include <vector>
+
+#include <boost/utility.hpp>
+
+#include <pbd/signals.h>
+
+#include "ardour/panner.h"
+#include "ardour/speakers.h"
+
+namespace ARDOUR {
+
+class Speakers;
+
+class VBAPSpeakers : public boost::noncopyable {
+public:
+       typedef std::vector<double> dvector;
+
+       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 VBAPSpeakers& instance (Speakers&);
+        uint32_t n_speakers() const { return _speakers.size(); }
+
+       ~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;
+
+       VBAPSpeakers (Speakers&);
+
+       struct azimuth_sorter {
+               bool operator() (const Speaker& s1, const Speaker& s2) {
+                       return s1.angles().azi < s2.angles().azi;
+               }
+       };
+
+       struct twoDmatrix : public dvector {
+       twoDmatrix() : dvector (4, 0.0) {}
+       };
+
+       struct threeDmatrix : public dvector {
+       threeDmatrix() : dvector (9, 0.0) {}
+       };
+        
+       struct tmatrix : public dvector {
+       tmatrix() : dvector (3, 0.0) {}
+       };
+
+       std::vector<dvector>  _matrices;       /* holds matrices for a given speaker combinations */
+       std::vector<tmatrix>  _speaker_tuples; /* holds speakers IDs for a given combination */
+
+       /* A struct for all loudspeakers */
+       struct ls_triplet_chain {
+               int ls_nos[3];
+               float inv_mx[9];
+               struct ls_triplet_chain *next;
+       };
+
+       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(PBD::CartesianVector v1,PBD::CartesianVector v2, PBD::CartesianVector *res);
+
+       void update ();
+       int  any_ls_inside_triplet (int a, int b, int c);
+       void add_ldsp_triplet (int i, int j, int k, struct ls_triplet_chain **ls_triplets);
+       int  lines_intersect (int i,int j,int k,int l);
+       void calculate_3x3_matrixes (struct ls_triplet_chain *ls_triplets);
+       void choose_speaker_triplets (struct ls_triplet_chain **ls_triplets);
+       void choose_speaker_pairs ();
+       void sort_2D_lss (int* sorted_lss);
+       int  calc_2D_inv_tmatrix (double azi1,double azi2, double* inv_mat);
+        
+};
+
+} /* namespace */
+
+#endif /* __libardour_vbap_speakers_h__ */
diff --git a/libs/panners/wscript b/libs/panners/wscript
new file mode 100644 (file)
index 0000000..63b547d
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+import autowaf
+import os
+
+# Mandatory variables
+srcdir = '.'
+blddir = 'build'
+
+#panners = [ '2in2out', 'vbap', '1in1out' ]
+panners = [ '2in2out' ]
+
+def set_options(opt):
+       autowaf.set_options(opt)
+
+def build(bld):
+        for i in panners:
+            bld.add_subdirs(i)
+