steps toward a working VBAP panner

git-svn-id: svn://localhost/ardour2/branches/3.0@8055 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Paul Davis 2010-11-18 18:01:30 +00:00
parent 6ce5dc2dc9
commit e50bd9e653
7 changed files with 377 additions and 170 deletions

View file

@ -1,34 +1,19 @@
/*
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.
/*
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__
@ -37,83 +22,13 @@
#include <string>
#include <map>
#include <pbd/signals.h>
#include "ardour/panner.h"
namespace ARDOUR {
class VBAPSpeakers {
public:
struct cart_vec {
float x;
float y;
float z;
};
struct ang_vec {
float azi;
float ele;
float length;
};
static const int MAX_TRIPLET_AMOUNT = 60;
VBAPSpeakers ();
~VBAPSpeakers ();
int add_speaker (double direction, double elevation = 0.0);
void remove_speaker (int id);
void move_speaker (int id, double direction, double elevation = 0.0);
const double* matrix (int tuple) const { return _matrices[tuple]; }
int speaker_for_tuple (int tuple, int which) const { return _speaker_tuples[tuple][which]; }
int n_tuples () const { return _matrices.size(); }
int dimension() const { return _dimension; }
static void angle_to_cart(ang_vec *from, cart_vec *to);
private:
static const double MIN_VOL_P_SIDE_LGTH = 0.01;
int _dimension;
/* A struct for a loudspeaker instance */
struct Speaker {
int id;
cart_vec coords;
ang_vec angles;
Speaker (int, double azimuth, double elevation);
void move (double azimuth, double elevation);
};
std::vector<Speaker> _speakers;
std::vector<double[9]> _matrices; /* holds matrices for a given speaker combinations */
std::vector<int[3]> _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(cart_vec v1, cart_vec v2);
static float vec_length(cart_vec v1);
static float vec_prod(cart_vec v1, cart_vec v2);
static float vol_p_side_lgth(int i, int j,int k, const std::vector<Speaker>&);
static void cross_prod(cart_vec v1,cart_vec v2, cart_vec *res);
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_ls_triplets (struct ls_triplet_chain **ls_triplets);
void choose_ls_pairs ();
void sort_2D_lss (int* sorted_lss);
int calc_2D_inv_tmatrix (double azi1,double azi2, double* inv_mat);
};
class VBAPSpeakers;
class VBAPanner : public StreamPanner {
public:
@ -124,20 +39,23 @@ class VBAPanner : public StreamPanner {
void set_azimuth_elevation (double azimuth, double elevation);
/* a utility function to convert azimuth+elevation into cartesian coordinates
as used by the StreamPanner API
*/
void azi_ele_to_cart (int azi, int ele, double* c);
private:
double _azimuth; /* direction for the signal source */
double _elevation; /* elevation of the signal source */
VBAPSpeakers& _speakers;
double _azimuth; /* direction for the signal source */
double _elevation; /* elevation of the signal source */
bool _dirty;
double gains[3];
double desired_gains[3];
int outputs[3];
int desired_outputs[3];
PBD::ScopedConnection speaker_connection;
VBAPSpeakers& _speakers;
void compute_gains (double g[3], int ls[3], int azi, int ele);
void update ();
void mark_dirty ();
};
} /* namespace */

View file

@ -0,0 +1,109 @@
/*
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 <map>
#include <pbd/signals.h>
#include "ardour/panner.h"
namespace ARDOUR {
class VBAPSpeakers {
public:
struct cart_vec {
float x;
float y;
float z;
};
struct ang_vec {
float azi;
float ele;
float length;
};
static const int MAX_TRIPLET_AMOUNT = 60;
VBAPSpeakers ();
~VBAPSpeakers ();
int add_speaker (double direction, double elevation = 0.0);
void remove_speaker (int id);
void move_speaker (int id, double direction, double elevation = 0.0);
const double* matrix (int tuple) const { return _matrices[tuple]; }
int speaker_for_tuple (int tuple, int which) const { return _speaker_tuples[tuple][which]; }
int n_tuples () const { return _matrices.size(); }
int dimension() const { return _dimension; }
static void angle_to_cart(ang_vec *from, cart_vec *to);
PBD::Signal0<void> Changed;
private:
static const double MIN_VOL_P_SIDE_LGTH = 0.01;
int _dimension;
/* A struct for a loudspeaker instance */
struct Speaker {
int id;
cart_vec coords;
ang_vec angles;
Speaker (int, double azimuth, double elevation);
void move (double azimuth, double elevation);
};
std::vector<Speaker> _speakers;
std::vector<double[9]> _matrices; /* holds matrices for a given speaker combinations */
std::vector<int[3]> _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(cart_vec v1, cart_vec v2);
static float vec_length(cart_vec v1);
static float vec_prod(cart_vec v1, cart_vec v2);
static float vol_p_side_lgth(int i, int j,int k, const std::vector<Speaker>&);
static void cross_prod(cart_vec v1,cart_vec v2, cart_vec *res);
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_ls_triplets (struct ls_triplet_chain **ls_triplets);
void choose_ls_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__ */

View file

@ -38,14 +38,24 @@
#include <string>
#include "ardour/vbap.h"
#include "pbd/cartesian.h"
#include "ardour/vbap.h"
#include "ardour/vbap_speakers.h"
#include "ardour/audio_buffer.h"
#include "ardour/buffer_set.h"
using namespace PBD;
using namespace ARDOUR;
using namespace std;
VBAPanner::VBAPanner (Panner& parent, Evoral::Parameter param, VBAPSpeakers& s)
: StreamPanner (parent, param)
, _dirty (false)
, _speakers (s)
{
_speakers.Changed.connect_same_thread (speaker_connection, boost::bind (&VBAPanner::mark_dirty, this));
}
VBAPanner::~VBAPanner ()
@ -53,21 +63,16 @@ VBAPanner::~VBAPanner ()
}
void
VBAPanner::azi_ele_to_cart (int azi, int ele, double* c)
VBAPanner::mark_dirty ()
{
static const double atorad = (2.0 * M_PI / 360.0) ;
c[0] = cos (azi * atorad) * cos (ele * atorad);
c[1] = sin (azi * atorad) * cos (ele * atorad);
c[2] = sin (ele * atorad);
_dirty = true;
}
void
VBAPanner::update ()
{
double g[3];
int ls[3];
compute_gains (g, ls, _azimuth, _elevation);
cart_to_azi_ele (_x, _y, _z, _azimuth, _elevation);
_dirty = true;
}
void
@ -80,23 +85,33 @@ VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
double small_g;
double big_sm_g, gtmp[3];
azi_ele_to_cart (azi,ele, cartdir);
azi_ele_to_cart (azi,ele, cartdir[0], cartdir[1], cartdir[2]);
big_sm_g = -100000.0;
for (i = 0; i < _speakers.n_tuples(); i++){
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)
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];
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);
@ -105,7 +120,7 @@ VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
speaker_ids[2] = _speakers.speaker_for_tuple (i, 2);
} else {
gains[2] = 0.0;
speaker_ids[2] = 0;
speaker_ids[2] = -1;
}
}
}
@ -115,9 +130,64 @@ VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
gains[0] /= power;
gains[1] /= power;
gains[2] /= power;
_dirty = false;
}
void
VBAPanner::do_distribute (AudioBuffer& bufs, BufferSet& obufs, gain_t gain_coefficient, nframes_t nframes)
VBAPanner::do_distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, nframes_t nframes)
{
if (_muted) {
return;
}
Sample* const src = srcbuf.data();
Sample* dst;
pan_t pan;
uint32_t n_audio = obufs.count().n_audio();
bool was_dirty;
if ((was_dirty = _dirty)) {
compute_gains (desired_gains, desired_outputs, _azimuth, _elevation);
}
bool todo[n_audio];
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 (outputs[o] != -1) {
nframes_t n = 0;
/* XXX TODO: interpolate across changes in gain and/or outputs
*/
dst = obufs.get_audio(outputs[o]).data();
pan = gain_coefficient * 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);
}
}
if (was_dirty) {
memcpy (gains, desired_gains, sizeof (gains));
memcpy (outputs, desired_outputs, sizeof (outputs));
}
}

View file

@ -34,7 +34,7 @@
#include <cmath>
#include <stdlib.h>
#include "ardour/vbap.h"
#include "ardour/vbap_speakers.h"
using namespace ARDOUR;
using namespace std;
@ -113,12 +113,14 @@ VBAPSpeakers::update ()
_dimension = dim;
if (_dimension == 3) {
ls_triplet_chain *ls_triplets = NULL;
ls_triplet_chain *ls_triplets = 0;
choose_ls_triplets (&ls_triplets);
calculate_3x3_matrixes (ls_triplets);
} else {
choose_ls_pairs ();
}
Changed (); /* EMIT SIGNAL */
}
void
@ -160,9 +162,9 @@ VBAPSpeakers::choose_ls_triplets(struct ls_triplet_chain **ls_triplets)
fprintf(stderr,"Number of loudspeakers is zero\nExiting\n");
exit(-1);
}
for(i=0;i<n_speakers;i++)
for(j=i+1;j<n_speakers;j++)
for(k=j+1;k<n_speakers;k++){
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;
@ -173,18 +175,24 @@ VBAPSpeakers::choose_ls_triplets(struct ls_triplet_chain **ls_triplets)
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++)
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) {
}
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)
while(distance_table[k] < distance) {
k++;
for(l=(table_size - 1);l > k ;l--){
}
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];
@ -200,33 +208,36 @@ VBAPSpeakers::choose_ls_triplets(struct ls_triplet_chain **ls_triplets)
/* 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++){
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){
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 = NULL;
while (trip_ptr != NULL){
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 != NULL) {
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;
@ -306,20 +317,20 @@ VBAPSpeakers::add_ldsp_triplet(int i, int j, int k, struct ls_triplet_chain **ls
struct ls_triplet_chain *trip_ptr, *prev;
trip_ptr = *ls_triplets;
prev = NULL;
prev = 0;
while (trip_ptr != NULL){
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 == NULL) {
if (prev == 0) {
*ls_triplets = trip_ptr;
} else {
prev->next = trip_ptr;
}
trip_ptr->next = NULL;
trip_ptr->next = 0;
trip_ptr->ls_nos[0] = i;
trip_ptr->ls_nos[1] = j;
trip_ptr->ls_nos[2] = k;
@ -463,7 +474,7 @@ VBAPSpeakers::calculate_3x3_matrixes(struct ls_triplet_chain *ls_triplets)
/* counting triplet amount */
while (tr_ptr != NULL) {
while (tr_ptr != 0) {
triplet_count++;
tr_ptr = tr_ptr->next;
}
@ -476,7 +487,7 @@ VBAPSpeakers::calculate_3x3_matrixes(struct ls_triplet_chain *ls_triplets)
_matrices.reserve (triplet_count);
_speaker_tuples.reserve (triplet_count);
while (tr_ptr != NULL) {
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);
@ -509,9 +520,9 @@ VBAPSpeakers::calculate_3x3_matrixes(struct ls_triplet_chain *ls_triplets)
_matrices[triplet][7] = invmx[7];
_matrices[triplet][8] = invmx[8];
_speaker_tuples[triplet][0] = tr_ptr->ls_nos[0]+1;
_speaker_tuples[triplet][1] = tr_ptr->ls_nos[1]+1;
_speaker_tuples[triplet][2] = tr_ptr->ls_nos[2]+1;
_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];
triplet++;
@ -553,7 +564,7 @@ VBAPSpeakers::choose_ls_pairs (){
}
}
if(((6.283 - _speakers[sorted_speakers[n_speakers-1]].angles.azi)
if (((6.283 - _speakers[sorted_speakers[n_speakers-1]].angles.azi)
+_speakers[sorted_speakers[0]].angles.azi) <= (M_PI - 0.175)) {
if(calc_2D_inv_tmatrix(_speakers[sorted_speakers[n_speakers-1]].angles.azi,
_speakers[sorted_speakers[0]].angles.azi,
@ -578,8 +589,8 @@ VBAPSpeakers::choose_ls_pairs (){
_matrices[pair][2] = inverse_matrix[speaker][2];
_matrices[pair][3] = inverse_matrix[speaker][3];
_speaker_tuples[pair][0] = sorted_speakers[speaker]+1;
_speaker_tuples[pair][1] = sorted_speakers[speaker+1]+1;
_speaker_tuples[pair][0] = sorted_speakers[speaker];
_speaker_tuples[pair][1] = sorted_speakers[speaker+1];
pair++;
}
@ -591,8 +602,8 @@ VBAPSpeakers::choose_ls_pairs (){
_matrices[pair][2] = inverse_matrix[speaker][2];
_matrices[pair][3] = inverse_matrix[speaker][3];
_speaker_tuples[pair][0] = sorted_speakers[n_speakers-1]+1;
_speaker_tuples[pair][1] = sorted_speakers[0]+1;
_speaker_tuples[pair][0] = sorted_speakers[n_speakers-1];
_speaker_tuples[pair][1] = sorted_speakers[0];
}
}

71
libs/pbd/cartesian.cc Normal file
View file

@ -0,0 +1,71 @@
/*
Copyright (C) 2010 Paul Davis
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <cmath>
#include "pbd/cartesian.h"
void
PBD::azi_ele_to_cart (double azi, double ele, double& x, double& y, double& z)
{
static const double atorad = 2.0 * M_PI / 360.0 ;
x = cos (azi * atorad) * cos (ele * atorad);
y = sin (azi * atorad) * cos (ele * atorad);
z = sin (ele * atorad);
}
void
PBD::cart_to_azi_ele (double x, double y, double z, double& azimuth, double& elevation)
{
/* converts cartesian coordinates to angular */
const double atorad = 2.0 * M_PI / 360.0;
double atan_y_per_x, atan_x_pl_y_per_z;
double distance;
if(x == 0.0) {
atan_y_per_x = M_PI / 2;
} else {
atan_y_per_x = atan(y / x);
}
azimuth = atan_y_per_x / atorad;
if (x < 0.0) {
azimuth +=180.0;
}
distance = sqrt (x*x + y*y);
if (z == 0.0) {
atan_x_pl_y_per_z = 0.0;
} else {
atan_x_pl_y_per_z = atan (z/distance);
}
if (distance == 0.0) {
if (z < 0.0) {
atan_x_pl_y_per_z = -M_PI/2.0;
} else {
atan_x_pl_y_per_z = M_PI/2.0;
}
}
elevation = atan_x_pl_y_per_z / atorad;
// distance = sqrtf (x*x + y*y + z*z);
}

27
libs/pbd/pbd/cartesian.h Normal file
View file

@ -0,0 +1,27 @@
/*
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 __libpbd_cartesian_h__
#define __libpbd_cartesian_h__
namespace PBD {
void azi_ele_to_cart (double azi, double ele, double& x, double& y, double& z);
void cart_to_azi_ele (double x, double y, double z, double& azi, double& ele);
}
#endif /* __libpbd_cartesian_h__ */

View file

@ -58,6 +58,7 @@ def build(bld):
basename.cc
base_ui.cc
boost_debug.cc
cartesian.cc
command.cc
convert.cc
controllable.cc