tentative commit of new panners subtree
git-svn-id: svn://localhost/ardour2/branches/3.0@8521 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
parent
a406d9183a
commit
2a8629d11c
|
@ -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);
|
||||
}
|
||||
|
|
@ -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__ */
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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__ */
|
|
@ -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()
|
||||
|
|
@ -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());
|
||||
}
|
|
@ -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__ */
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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__ */
|
|
@ -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)
|
||||
|
Loading…
Reference in New Issue