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:
Paul Davis 2011-01-17 17:51:44 +00:00
parent a406d9183a
commit 2a8629d11c
10 changed files with 2060 additions and 0 deletions

View File

@ -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);
}

View File

@ -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__ */

View File

@ -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;
}

View File

@ -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__ */

View File

@ -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()

306
libs/panners/vbap/vbap.cc Normal file
View File

@ -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());
}

90
libs/panners/vbap/vbap.h Normal file
View File

@ -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__ */

View File

@ -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;
}
}

View File

@ -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__ */

18
libs/panners/wscript Normal file
View File

@ -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)