2 This software is being provided to you, the licensee, by Ville Pulkki,
3 under the following license. By obtaining, using and/or copying this
4 software, you agree that you have read, understood, and will comply
5 with these terms and conditions: Permission to use, copy, modify and
6 distribute, including the right to grant others rights to distribute
7 at any tier, this software and its documentation for any purpose and
8 without fee or royalty is hereby granted, provided that you agree to
9 comply with the following copyright notice and statements, including
10 the disclaimer, and that the same appear on ALL copies of the software
11 and documentation, including modifications that you make for internal
12 use or for distribution:
14 Copyright 1998 by Ville Pulkki, Helsinki University of Technology. All
17 The software may be used, distributed, and included to commercial
18 products without any charges. When included to a commercial product,
19 the method "Vector Base Amplitude Panning" and its developer Ville
20 Pulkki must be referred to in documentation.
22 This software is provided "as is", and Ville Pulkki or Helsinki
23 University of Technology make no representations or warranties,
24 expressed or implied. By way of example, but not limitation, Helsinki
25 University of Technology or Ville Pulkki make no representations or
26 warranties of merchantability or fitness for any particular purpose or
27 that the use of the licensed software or documentation will not
28 infringe any third party patents, copyrights, trademarks or other
29 rights. The name of Ville Pulkki or Helsinki University of Technology
30 may not be used in advertising or publicity pertaining to distribution
39 #include "pbd/cartesian.h"
41 #include "vbap_speakers.h"
43 using namespace ARDOUR;
47 const double VBAPSpeakers::MIN_VOL_P_SIDE_LGTH = 0.01;
49 VBAPSpeakers::VBAPSpeakers (boost::shared_ptr<Speakers> s)
53 _parent->Changed.connect_same_thread (speaker_connection, boost::bind (&VBAPSpeakers::update, this));
57 VBAPSpeakers::~VBAPSpeakers ()
62 VBAPSpeakers::update ()
66 _speakers = _parent->speakers();
68 for (vector<Speaker>::const_iterator i = _speakers.begin(); i != _speakers.end(); ++i) {
69 if ((*i).angles().ele != 0.0) {
77 if (_speakers.size() < 2) {
78 /* nothing to be done with less than two speakers */
82 if (_dimension == 3) {
83 ls_triplet_chain *ls_triplets = 0;
84 choose_speaker_triplets (&ls_triplets);
86 calculate_3x3_matrixes (ls_triplets);
90 choose_speaker_pairs ();
95 VBAPSpeakers::choose_speaker_triplets(struct ls_triplet_chain **ls_triplets)
97 /* Selects the loudspeaker triplets, and
98 calculates the inversion matrices for each selected triplet.
99 A line (connection) is drawn between each loudspeaker. The lines
100 denote the sides of the triangles. The triangles should not be
101 intersecting. All crossing connections are searched and the
102 longer connection is erased. This yields non-intesecting triangles,
103 which can be used in panning.
107 for (vector<Speaker>::iterator i = _speakers.begin(); i != _speakers.end(); ++i) {
108 cout << "Speaker " << (*i).id << " @ "
109 << (*i).coords().x << ", " << (*i).coords().y << ", " << (*i).coords().z
110 << " azimuth " << (*i).angles().azi
111 << " elevation " << (*i).angles().ele
112 << " distance " << (*i).angles().length
117 int i,j,k,l,table_size;
118 int n_speakers = _speakers.size ();
119 /* variable length arrays arrived in C99, became optional in C11, and
120 are only planned for C++14. Use alloca which is functionally
121 identical (but uglier to read).
123 int** connections = (int**) alloca (sizeof (int) * n_speakers * n_speakers);
124 float* distance_table = (float *) alloca (sizeof (float) * ((n_speakers * (n_speakers - 1)) / 2));
125 int* distance_table_i = (int *) alloca (sizeof (int) * ((n_speakers * (n_speakers - 1)) / 2));
126 int* distance_table_j = (int *) alloca (sizeof (int) * ((n_speakers * (n_speakers - 1)) / 2));
128 struct ls_triplet_chain *trip_ptr, *prev, *tmp_ptr;
130 if (n_speakers == 0) {
134 for (i = 0; i < n_speakers; i++) {
135 for (j = i+1; j < n_speakers; j++) {
136 for(k = j+1; k < n_speakers; k++) {
137 if (vol_p_side_lgth(i, j, k, _speakers) > MIN_VOL_P_SIDE_LGTH) {
144 add_ldsp_triplet(i,j,k,ls_triplets);
150 /*calculate distancies between all speakers and sorting them*/
151 table_size =(((n_speakers - 1) * (n_speakers)) / 2);
152 for (i = 0; i < table_size; i++) {
153 distance_table[i] = 100000.0;
156 for (i = 0;i < n_speakers; i++) {
157 for (j = i+1; j < n_speakers; j++) {
158 if (connections[i][j] == 1) {
159 distance = fabs(vec_angle(_speakers[i].coords(),_speakers[j].coords()));
161 while(distance_table[k] < distance) {
164 for (l = table_size - 1; l > k ; l--) {
165 distance_table[l] = distance_table[l-1];
166 distance_table_i[l] = distance_table_i[l-1];
167 distance_table_j[l] = distance_table_j[l-1];
169 distance_table[k] = distance;
170 distance_table_i[k] = i;
171 distance_table_j[k] = j;
177 /* disconnecting connections which are crossing shorter ones,
178 starting from shortest one and removing all that cross it,
179 and proceeding to next shortest */
180 for (i = 0; i < table_size; i++) {
181 int fst_ls = distance_table_i[i];
182 int sec_ls = distance_table_j[i];
183 if (connections[fst_ls][sec_ls] == 1) {
184 for (j = 0; j < n_speakers; j++) {
185 for (k = j+1; k < n_speakers; k++) {
186 if ((j != fst_ls) && (k != sec_ls) && (k != fst_ls) && (j != sec_ls)) {
187 if (lines_intersect(fst_ls, sec_ls, j, k) == 1){
188 connections[j][k] = 0;
189 connections[k][j] = 0;
197 /* remove triangles which had crossing sides
198 with smaller triangles or include loudspeakers*/
199 trip_ptr = *ls_triplets;
201 while (trip_ptr != 0){
202 i = trip_ptr->ls_nos[0];
203 j = trip_ptr->ls_nos[1];
204 k = trip_ptr->ls_nos[2];
205 if (connections[i][j] == 0 ||
206 connections[i][k] == 0 ||
207 connections[j][k] == 0 ||
208 any_ls_inside_triplet(i,j,k) == 1 ){
210 prev->next = trip_ptr->next;
212 trip_ptr = trip_ptr->next;
215 *ls_triplets = trip_ptr->next;
217 trip_ptr = trip_ptr->next;
222 trip_ptr = trip_ptr->next;
229 VBAPSpeakers::any_ls_inside_triplet(int a, int b, int c)
231 /* returns 1 if there is loudspeaker(s) inside given ls triplet */
233 const CartesianVector* lp1;
234 const CartesianVector* lp2;
235 const CartesianVector* lp3;
241 int n_speakers = _speakers.size();
243 lp1 = &(_speakers[a].coords());
244 lp2 = &(_speakers[b].coords());
245 lp3 = &(_speakers[c].coords());
247 /* matrix inversion */
248 invdet = 1.0 / ( lp1->x * ((lp2->y * lp3->z) - (lp2->z * lp3->y))
249 - lp1->y * ((lp2->x * lp3->z) - (lp2->z * lp3->x))
250 + lp1->z * ((lp2->x * lp3->y) - (lp2->y * lp3->x)));
252 invmx[0] = ((lp2->y * lp3->z) - (lp2->z * lp3->y)) * invdet;
253 invmx[3] = ((lp1->y * lp3->z) - (lp1->z * lp3->y)) * -invdet;
254 invmx[6] = ((lp1->y * lp2->z) - (lp1->z * lp2->y)) * invdet;
255 invmx[1] = ((lp2->x * lp3->z) - (lp2->z * lp3->x)) * -invdet;
256 invmx[4] = ((lp1->x * lp3->z) - (lp1->z * lp3->x)) * invdet;
257 invmx[7] = ((lp1->x * lp2->z) - (lp1->z * lp2->x)) * -invdet;
258 invmx[2] = ((lp2->x * lp3->y) - (lp2->y * lp3->x)) * invdet;
259 invmx[5] = ((lp1->x * lp3->y) - (lp1->y * lp3->x)) * -invdet;
260 invmx[8] = ((lp1->x * lp2->y) - (lp1->y * lp2->x)) * invdet;
262 any_ls_inside = false;
263 for (i = 0; i < n_speakers; i++) {
264 if (i != a && i!=b && i != c) {
266 for (j = 0; j < 3; j++) {
267 tmp = _speakers[i].coords().x * invmx[0 + j*3];
268 tmp += _speakers[i].coords().y * invmx[1 + j*3];
269 tmp += _speakers[i].coords().z * invmx[2 + j*3];
275 any_ls_inside = true;
280 return any_ls_inside;
285 VBAPSpeakers::add_ldsp_triplet(int i, int j, int k, struct ls_triplet_chain **ls_triplets)
287 /* adds i,j,k triplet to triplet chain*/
289 struct ls_triplet_chain *trip_ptr, *prev;
290 trip_ptr = *ls_triplets;
293 while (trip_ptr != 0){
295 trip_ptr = trip_ptr->next;
298 trip_ptr = (struct ls_triplet_chain*) malloc (sizeof (struct ls_triplet_chain));
301 *ls_triplets = trip_ptr;
303 prev->next = trip_ptr;
307 trip_ptr->ls_nos[0] = i;
308 trip_ptr->ls_nos[1] = j;
309 trip_ptr->ls_nos[2] = k;
313 VBAPSpeakers::vec_angle(CartesianVector v1, CartesianVector v2)
315 double inner= ((v1.x*v2.x + v1.y*v2.y + v1.z*v2.z)/
316 (vec_length(v1) * vec_length(v2)));
326 return fabs(acos(inner));
330 VBAPSpeakers::vec_length(CartesianVector v1)
332 double rv = sqrt(v1.x*v1.x + v1.y*v1.y + v1.z*v1.z);
333 if (rv > 1e-14) return rv;
338 VBAPSpeakers::vec_prod(CartesianVector v1, CartesianVector v2)
340 return (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z);
344 VBAPSpeakers::vol_p_side_lgth(int i, int j, int k, const vector<Speaker>& speakers)
346 /* calculate volume of the parallelepiped defined by the loudspeaker
347 direction vectors and divide it with total length of the triangle sides.
348 This is used when removing too narrow triangles. */
351 CartesianVector xprod;
352 cross_prod (speakers[i].coords(), speakers[j].coords(), &xprod);
353 volper = fabs (vec_prod(xprod, speakers[k].coords()));
354 lgth = ( fabs (vec_angle(speakers[i].coords(), speakers[j].coords()))
355 + fabs (vec_angle(speakers[i].coords(), speakers[k].coords()))
356 + fabs (vec_angle(speakers[j].coords(), speakers[k].coords())));
357 if (lgth > 0.00001) {
358 return volper / lgth;
365 VBAPSpeakers::cross_prod(CartesianVector v1,CartesianVector v2, CartesianVector *res)
369 res->x = (v1.y * v2.z) - (v1.z * v2.y);
370 res->y = (v1.z * v2.x) - (v1.x * v2.z);
371 res->z = (v1.x * v2.y) - (v1.y * v2.x);
373 length = vec_length(*res);
386 VBAPSpeakers::lines_intersect (int i, int j, int k, int l)
388 /* checks if two lines intersect on 3D sphere
389 see theory in paper Pulkki, V. Lokki, T. "Creating Auditory Displays
390 with Multiple Loudspeakers Using VBAP: A Case Study with
391 DIVA Project" in International Conference on
392 Auditory Displays -98. E-mail Ville.Pulkki@hut.fi
393 if you want to have that paper.
398 CartesianVector v3, neg_v3;
399 float dist_ij,dist_kl,dist_iv3,dist_jv3,dist_inv3,dist_jnv3;
400 float dist_kv3,dist_lv3,dist_knv3,dist_lnv3;
402 cross_prod(_speakers[i].coords(),_speakers[j].coords(),&v1);
403 cross_prod(_speakers[k].coords(),_speakers[l].coords(),&v2);
404 cross_prod(v1,v2,&v3);
406 neg_v3.x= 0.0 - v3.x;
407 neg_v3.y= 0.0 - v3.y;
408 neg_v3.z= 0.0 - v3.z;
410 dist_ij = (vec_angle(_speakers[i].coords(),_speakers[j].coords()));
411 dist_kl = (vec_angle(_speakers[k].coords(),_speakers[l].coords()));
412 dist_iv3 = (vec_angle(_speakers[i].coords(),v3));
413 dist_jv3 = (vec_angle(v3,_speakers[j].coords()));
414 dist_inv3 = (vec_angle(_speakers[i].coords(),neg_v3));
415 dist_jnv3 = (vec_angle(neg_v3,_speakers[j].coords()));
416 dist_kv3 = (vec_angle(_speakers[k].coords(),v3));
417 dist_lv3 = (vec_angle(v3,_speakers[l].coords()));
418 dist_knv3 = (vec_angle(_speakers[k].coords(),neg_v3));
419 dist_lnv3 = (vec_angle(neg_v3,_speakers[l].coords()));
421 /* if one of loudspeakers is close to crossing point, don't do anything*/
422 if(fabsf(dist_iv3) <= 0.01 || fabsf(dist_jv3) <= 0.01 ||
423 fabsf(dist_kv3) <= 0.01 || fabsf(dist_lv3) <= 0.01 ||
424 fabsf(dist_inv3) <= 0.01 || fabsf(dist_jnv3) <= 0.01 ||
425 fabsf(dist_knv3) <= 0.01 || fabsf(dist_lnv3) <= 0.01 ) {
429 /* if crossing point is on line between both loudspeakers return 1 */
430 if (((fabsf(dist_ij - (dist_iv3 + dist_jv3)) <= 0.01 ) &&
431 (fabsf(dist_kl - (dist_kv3 + dist_lv3)) <= 0.01)) ||
432 ((fabsf(dist_ij - (dist_inv3 + dist_jnv3)) <= 0.01) &&
433 (fabsf(dist_kl - (dist_knv3 + dist_lnv3)) <= 0.01 ))) {
441 VBAPSpeakers::calculate_3x3_matrixes(struct ls_triplet_chain *ls_triplets)
443 /* Calculates the inverse matrices for 3D */
445 const CartesianVector* lp1;
446 const CartesianVector* lp2;
447 const CartesianVector* lp3;
449 struct ls_triplet_chain *tr_ptr = ls_triplets;
450 int triplet_count = 0;
455 /* counting triplet amount */
457 while (tr_ptr != 0) {
459 tr_ptr = tr_ptr->next;
463 cerr << "@@@ VBAP triplets generate " << triplet_count << " of speaker tuples\n";
469 _speaker_tuples.clear ();
471 for (int n = 0; n < triplet_count; ++n) {
472 _matrices.push_back (threeDmatrix());
473 _speaker_tuples.push_back (tmatrix());
476 tr_ptr = ls_triplets;
477 while (tr_ptr != 0) {
478 lp1 = &(_speakers[tr_ptr->ls_nos[0]].coords());
479 lp2 = &(_speakers[tr_ptr->ls_nos[1]].coords());
480 lp3 = &(_speakers[tr_ptr->ls_nos[2]].coords());
482 /* matrix inversion */
483 invmx = tr_ptr->inv_mx;
484 invdet = 1.0 / ( lp1->x * ((lp2->y * lp3->z) - (lp2->z * lp3->y))
485 - lp1->y * ((lp2->x * lp3->z) - (lp2->z * lp3->x))
486 + lp1->z * ((lp2->x * lp3->y) - (lp2->y * lp3->x)));
488 invmx[0] = ((lp2->y * lp3->z) - (lp2->z * lp3->y)) * invdet;
489 invmx[3] = ((lp1->y * lp3->z) - (lp1->z * lp3->y)) * -invdet;
490 invmx[6] = ((lp1->y * lp2->z) - (lp1->z * lp2->y)) * invdet;
491 invmx[1] = ((lp2->x * lp3->z) - (lp2->z * lp3->x)) * -invdet;
492 invmx[4] = ((lp1->x * lp3->z) - (lp1->z * lp3->x)) * invdet;
493 invmx[7] = ((lp1->x * lp2->z) - (lp1->z * lp2->x)) * -invdet;
494 invmx[2] = ((lp2->x * lp3->y) - (lp2->y * lp3->x)) * invdet;
495 invmx[5] = ((lp1->x * lp3->y) - (lp1->y * lp3->x)) * -invdet;
496 invmx[8] = ((lp1->x * lp2->y) - (lp1->y * lp2->x)) * invdet;
498 /* copy the matrix */
500 _matrices[triplet][0] = invmx[0];
501 _matrices[triplet][1] = invmx[1];
502 _matrices[triplet][2] = invmx[2];
503 _matrices[triplet][3] = invmx[3];
504 _matrices[triplet][4] = invmx[4];
505 _matrices[triplet][5] = invmx[5];
506 _matrices[triplet][6] = invmx[6];
507 _matrices[triplet][7] = invmx[7];
508 _matrices[triplet][8] = invmx[8];
510 _speaker_tuples[triplet][0] = tr_ptr->ls_nos[0];
511 _speaker_tuples[triplet][1] = tr_ptr->ls_nos[1];
512 _speaker_tuples[triplet][2] = tr_ptr->ls_nos[2];
515 cerr << "Triplet[" << triplet << "] = "
516 << tr_ptr->ls_nos[0] << " + "
517 << tr_ptr->ls_nos[1] << " + "
518 << tr_ptr->ls_nos[2] << endl;
523 tr_ptr = tr_ptr->next;
528 VBAPSpeakers::choose_speaker_pairs (){
530 /* selects the loudspeaker pairs, calculates the inversion
531 matrices and stores the data to a global array
533 const int n_speakers = _speakers.size();
534 const double AZIMUTH_DELTA_THRESHOLD_DEGREES = (180.0/M_PI) * (M_PI - 0.175);
535 /* variable length arrays arrived in C99, became optional in C11, and
536 are only planned for C++14. Use alloca which is functionally
537 identical (but uglier to read).
539 int* sorted_speakers = (int*) alloca (sizeof (int) * n_speakers);
540 bool* exists = (bool*) alloca (sizeof(bool) * n_speakers);
541 double** inverse_matrix = (double**) alloca (sizeof (double) * n_speakers * 4);
542 int expected_pairs = 0;
547 if (n_speakers == 0) {
551 for (speaker = 0; speaker < n_speakers; ++speaker) {
552 exists[speaker] = false;
555 /* sort loudspeakers according their aximuth angle */
556 sort_2D_lss (sorted_speakers);
558 /* adjacent loudspeakers are the loudspeaker pairs to be used.*/
559 for (speaker = 0; speaker < n_speakers-1; speaker++) {
561 if ((_speakers[sorted_speakers[speaker+1]].angles().azi -
562 _speakers[sorted_speakers[speaker]].angles().azi) <= AZIMUTH_DELTA_THRESHOLD_DEGREES) {
563 if (calc_2D_inv_tmatrix( _speakers[sorted_speakers[speaker]].angles().azi,
564 _speakers[sorted_speakers[speaker+1]].angles().azi,
565 inverse_matrix[speaker]) != 0){
566 exists[speaker] = true;
572 if (((6.283 - _speakers[sorted_speakers[n_speakers-1]].angles().azi)
573 +_speakers[sorted_speakers[0]].angles().azi) <= AZIMUTH_DELTA_THRESHOLD_DEGREES) {
574 if (calc_2D_inv_tmatrix(_speakers[sorted_speakers[n_speakers-1]].angles().azi,
575 _speakers[sorted_speakers[0]].angles().azi,
576 inverse_matrix[n_speakers-1]) != 0) {
577 exists[n_speakers-1] = true;
585 _speaker_tuples.clear ();
587 for (int n = 0; n < expected_pairs; ++n) {
588 _matrices.push_back (twoDmatrix());
589 _speaker_tuples.push_back (tmatrix());
592 for (speaker = 0; speaker < n_speakers - 1; speaker++) {
593 if (exists[speaker]) {
594 _matrices[pair][0] = inverse_matrix[speaker][0];
595 _matrices[pair][1] = inverse_matrix[speaker][1];
596 _matrices[pair][2] = inverse_matrix[speaker][2];
597 _matrices[pair][3] = inverse_matrix[speaker][3];
599 _speaker_tuples[pair][0] = sorted_speakers[speaker];
600 _speaker_tuples[pair][1] = sorted_speakers[speaker+1];
606 if (exists[n_speakers-1]) {
607 _matrices[pair][0] = inverse_matrix[speaker][0];
608 _matrices[pair][1] = inverse_matrix[speaker][1];
609 _matrices[pair][2] = inverse_matrix[speaker][2];
610 _matrices[pair][3] = inverse_matrix[speaker][3];
612 _speaker_tuples[pair][0] = sorted_speakers[n_speakers-1];
613 _speaker_tuples[pair][1] = sorted_speakers[0];
618 VBAPSpeakers::sort_2D_lss (int* sorted_speakers)
620 vector<Speaker> tmp = _speakers;
621 vector<Speaker>::iterator s;
622 azimuth_sorter sorter;
625 sort (tmp.begin(), tmp.end(), sorter);
627 for (n = 0, s = tmp.begin(); s != tmp.end(); ++s, ++n) {
628 sorted_speakers[n] = (*s).id;
633 VBAPSpeakers::calc_2D_inv_tmatrix (double azi1, double azi2, double* inverse_matrix)
638 x1 = cos (azi1 * (M_PI/180.0));
639 x2 = sin (azi1 * (M_PI/180.0));
640 x3 = cos (azi2 * (M_PI/180.0));
641 x4 = sin (azi2 * (M_PI/180.0));
642 det = (x1 * x4) - ( x3 * x2 );
644 if (fabs(det) <= 0.001) {
646 inverse_matrix[0] = 0.0;
647 inverse_matrix[1] = 0.0;
648 inverse_matrix[2] = 0.0;
649 inverse_matrix[3] = 0.0;
655 inverse_matrix[0] = x4 / det;
656 inverse_matrix[1] = -x3 / det;
657 inverse_matrix[2] = -x2 / det;
658 inverse_matrix[3] = x1 / det;