From f0c2ea083dbe6b3c8491dbd151430892ec5584b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juha=20Vehvil=C3=A4inen?= Date: Thu, 20 Jun 2002 11:32:15 +0000 Subject: This commit was generated by cvs2svn to compensate for changes in r17, which included commits to RCS files with non-trunk default branches. svn path=/trunk/externals/vbap/; revision=18 --- COPYRIGHT | 37 ++ define_loudspeakers.c | 852 +++++++++++++++++++++++++++++++++++++++++++ define_loudspeakers.dll | Bin 0 -> 45056 bytes define_loudspeakers.pd_irix6 | Bin 0 -> 29312 bytes graph-to-aziele.pd | 51 +++ high.pd | 27 ++ makefile.irix | 12 + makefile.nt | 89 +++++ playsample~.pd | 56 +++ recent.pd | 14 + so_locations | 6 + vbap-demo.pd | 41 +++ vbap-help.pd | 52 +++ vbap.c | 650 +++++++++++++++++++++++++++++++++ vbap.dll | Bin 0 -> 40960 bytes vbap.main.pd | 92 +++++ vbap.pd_irix6 | Bin 0 -> 27025 bytes vbapmodule.pd | 22 ++ vbapsnd.pd | 68 ++++ 19 files changed, 2069 insertions(+) create mode 100644 COPYRIGHT create mode 100644 define_loudspeakers.c create mode 100644 define_loudspeakers.dll create mode 100755 define_loudspeakers.pd_irix6 create mode 100644 graph-to-aziele.pd create mode 100644 high.pd create mode 100644 makefile.irix create mode 100644 makefile.nt create mode 100644 playsample~.pd create mode 100644 recent.pd create mode 100644 so_locations create mode 100644 vbap-demo.pd create mode 100644 vbap-help.pd create mode 100644 vbap.c create mode 100644 vbap.dll create mode 100644 vbap.main.pd create mode 100755 vbap.pd_irix6 create mode 100644 vbapmodule.pd create mode 100644 vbapsnd.pd diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..d22d3d1 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,37 @@ +Copyright + +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-2002 by Ville Pulkki. All rights reserved. + +Written by Ville Pulkki +Port to Pure Data by Juha Vehviläinen +Helsinki University of Technology +Laboratory of acoustics and audio signal processing + + +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. diff --git a/define_loudspeakers.c b/define_loudspeakers.c new file mode 100644 index 0000000..1b1c757 --- /dev/null +++ b/define_loudspeakers.c @@ -0,0 +1,852 @@ +/* define_loudspeakers.c + +written by Ville Pulkki 1999 +Helsinki University of Technology +and +Unversity of California at Berkeley + +See copyright in file with name COPYRIGHT */ + +#include +#include "m_pd.h" /* you must include this - it contains the external object's link to pure data */ + +#define RES_ID 9172 /* resource ID for assistance (we'll add that later) */ +#define MAX_LS_AMOUNT 55 /* maximum amount of loudspeakers, can be raised */ +#define MIN_VOL_P_SIDE_LGTH 0.01 + +#ifndef NT +#define NULL 0L +#endif +#ifdef NT +#define fabsf fabs +#endif + +/* A struct for a loudspeaker instance */ +typedef struct { /* distance value is 1.0 == unit vectors */ + float x; /* cartesian coordinates */ + float y; + float z; + float azi; /* polar coordinates */ + float ele; + int channel_nbr; /* which speaker channel number */ +} t_ls; + +/* A struct for all loudspeaker sets */ +typedef struct t_ls_set { + int ls_nos[3]; /* channel numbers */ + float inv_mx[9]; /* inverse 3x3 or 2x2 matrix */ + struct t_ls_set *next; /* next set (triplet or pair) */ +} t_ls_set; + +typedef struct /* This defines the object as an entity made up of other things */ +{ + t_object x_ob; /* gotta say this... it creates a reference to your object */ + long x_ls_read; /* 1 if loudspeaker directions have been read */ + long x_triplets_specified; /* 1 if loudspeaker triplets have been chosen */ + t_ls x_ls[MAX_LS_AMOUNT]; /* loudspeakers */ + t_ls_set *x_ls_set; /* loudspeaker sets */ + void *x_outlet0; /* outlet creation - inlets are automatic */ + long x_ls_amount; /* number of loudspeakers */ + long x_dimension; /* 2 (horizontal arrays) or 3 (3d setups) */ +} t_def_ls; + +static t_class *def_ls_class; /* so max can identify your object */ +void def_ls_bang(t_def_ls *x); +void def_ls_int(t_def_ls *x, t_float n); +void def_ls_read_directions(t_def_ls *x, t_symbol *s, int ac, t_atom *av); +void def_ls_read_triplets(t_def_ls *x, t_symbol *s, int ac, t_atom *av); +static void *def_ls_new(t_symbol *s, int ac, t_atom *av); /* using A_GIMME - typed message list */ +void def_ls(float g[3], long ls[3], t_def_ls *x); +void ls_angles_to_cart(t_ls *ls); +void choose_ls_triplets(t_def_ls *x); +int any_ls_inside_triplet(int a, int b, int c,t_ls lss[MAX_LS_AMOUNT],int ls_amount); +void add_ldsp_triplet(int i, int j, int k, t_def_ls *x); +float vec_angle(t_ls v1, t_ls v2); +float vec_length(t_ls v1); +float vec_prod(t_ls v1, t_ls v2); +float vec_prod(t_ls v1, t_ls v2); +float vol_p_side_lgth(int i, int j,int k, t_ls lss[MAX_LS_AMOUNT] ); +void unq_cross_prod(t_ls v1,t_ls v2, t_ls *res); +int lines_intersect(int i,int j,int k,int l,t_ls lss[MAX_LS_AMOUNT]); +void calculate_3x3_matrixes(t_def_ls *x); +void choose_ls_tuplets(t_def_ls *x); +int calc_2D_inv_tmatrix(float azi1,float azi2, float inv_mat[4]); +void sort_2D_lss(t_ls lss[MAX_LS_AMOUNT], int sorted_lss[MAX_LS_AMOUNT], + int ls_amount); + +/* above are the prototypes for the methods/procedures/functions you will use */ + +void define_loudspeakers_setup(void) +{ + def_ls_class = class_new(gensym("define_loudspeakers"), (t_newmethod)def_ls_new, 0, (short)sizeof(t_def_ls), 0, A_GIMME, 0); + /* def_ls_new = creation function, A_DEFLONG = its (optional) arguement is a long (32-bit) int */ +/* +/* addbang((method)def_ls_bang); /* the procedure it uses when it gets a bang in the left inlet */ +/* addint((method)def_ls_int); /* the rocedure for an int in the left inlet (inlet 0) */ +/* addmess((method)def_ls_read_directions, "ls-directions", A_GIMME, 0); +/* addmess((method)def_ls_read_triplets, "ls-triplets", A_GIMME, 0); +*/ + class_addbang(def_ls_class, def_ls_bang); + class_addfloat(def_ls_class, def_ls_int); + class_addmethod(def_ls_class, (t_method)def_ls_read_directions, gensym("ls-directions"), A_GIMME, 0); + class_addmethod(def_ls_class, (t_method)def_ls_read_triplets, gensym("ls-triplets"), A_GIMME, 0); +} + + +void def_ls_bang(t_def_ls *x) /* x = reference to this instance of the object */ +{ /* calculate and print out chosen loudspeaker sets and corresponding matrices */ + t_atom at[MAX_LS_AMOUNT*2+1]; + float g[3]; + long ls[3]; + long i; + + if(x->x_ls_read == 1){ + if(x->x_ls_amount < x->x_dimension){ + post("define-loudspeakers: Too few loudspeakers!",0); + return; + } else if(x->x_dimension == 3){ + if(x->x_triplets_specified==0) + choose_ls_triplets(x); + calculate_3x3_matrixes(x); + } else if(x->x_dimension == 2) + choose_ls_tuplets(x); + else { + post("define-loudspeakers: Error in loudspeaker direction data"); + post("dimension azimuth1 [elevation1] azimuth2 [elevation2]..."); + post("dimension == 2 for horizontal ls arrays"); + post("dimension == 3 for 3-D ls arrays (speakers also upward and/or downward ",0); + } + } else{ + post("define-loudspeakers: Error in loudspeaker direction data",0); + post("dimension azimuth1 [elevation1] azimuth2 [elevation2]...",0); + post("dimension == 2 for horizontal ls arrays",0); + post("dimension == 3 for 3-D ls arrays (speakers also upward and/or downward ",0); + } +} + +/*--------------------------------------------------------------------------*/ + +void def_ls_int(t_def_ls *x, t_float n) /* x = the instance of the object, n = the int received in the right inlet */ +{ + /* do something if an int comes in the left inlet??? */ +} + +void def_ls_read_triplets(t_def_ls *x, t_symbol *s, int ac, t_atom *av) +/* when loudspeaker triplets come in a message */ +{ + long l1,l2,l3,i; + t_ls_set *trip_ptr, *tmp_ptr, *prev; + if(x->x_ls_read == 0){ + post("define_loudspeakers: Define loudspeaker directions first!",0); + return; + } + + if(x->x_dimension == 2){ + post("define_loudspeakers: Can't specify loudspeaker triplets in 2-D setup!",0); + return; + } + + trip_ptr = x->x_ls_set; + prev = NULL; + while (trip_ptr != NULL){ + tmp_ptr = trip_ptr; + trip_ptr = trip_ptr->next; + freebytes(tmp_ptr, sizeof (struct t_ls_set)); + } + x->x_ls_set = NULL; + + for(i=0;ix_triplets_specified=1; +} + + + +void def_ls_read_directions(t_def_ls *x, t_symbol *s, int ac, t_atom *av) +/* when loudspeaker directions come in a message */ +{ + t_ls_set *trip_ptr, *prev, *tmp_ptr; + long i,pointer,newlyread; + newlyread = 0; +/* if(av[0].a_type == A_LONG){ + x->x_dimension= av[0].a_w.w_long; + newlyread = 1; + } else */ + if(av[0].a_type == A_FLOAT) { + x->x_dimension= (int) av[0].a_w.w_float; + newlyread = 1; + } else x->x_dimension= 0; + + if(x->x_dimension <2 || x->x_dimension >3){ + post("define-loudspeakers: Dimension has to be 2 or 3!",0); + return; + } + + pointer = 1; + x->x_ls_amount= (ac-1) / (x->x_dimension - 1); + for(i=0; i < x->x_ls_amount;i++){ +/* if(av[0].a_type == A_LONG) + x->x_ls[i].azi = (float) av[pointer].a_w.w_long; + else */ + if(av[0].a_type == A_FLOAT) + x->x_ls[i].azi = av[pointer].a_w.w_float; + else { + post("define-loudspeakers: Error in loudspeaker data!",0); + newlyread =0; + return; + } + pointer++; + if(x->x_dimension == 3){ +/* if(av[0].a_type == A_LONG) + x->x_ls[i].ele = (float) av[pointer].a_w.w_long; + else */ + if(av[0].a_type == A_FLOAT) + x->x_ls[i].ele = av[pointer].a_w.w_float; + else { + post("define-loudspeakers: Error in loudspeaker data!",0); + newlyread =0; + return; + } + pointer++; + } else + x->x_ls[i].ele = 0.0; /* 2-D case */ + } + + if(newlyread == 1){ + x->x_ls_read = 1; /* preprocess data */ + for(i=0;ix_ls_amount;i++) + ls_angles_to_cart(&x->x_ls[i]); + trip_ptr = x->x_ls_set; + prev = NULL; + while (trip_ptr != NULL){ /* remove old matrices */ + tmp_ptr = trip_ptr; + trip_ptr = trip_ptr->next; + freebytes(tmp_ptr, sizeof (struct t_ls_set)); + } + x->x_ls_set = NULL; + } + x->x_triplets_specified=0; +} + +/*--------------------------------------------------------------------------*/ + +void ls_angles_to_cart(t_ls *ls) +/* convert angular direction to cartesian */ +{ + float atorad = (2 * 3.1415927 / 360) ; + float azi = ls->azi; + float ele = ls->ele; + ls->x = cos((float) azi * atorad) * cos((float) ele * atorad); + ls->y = sin((float) azi * atorad) * cos((float) ele * atorad); + ls->z = sin((float) ele * atorad); +} + +static void *def_ls_new(t_symbol *s, int ac, t_atom *av) /* create new instance of object... MUST + send it an int even if you do nothing with this int!! */ +{ + long i,pointer; + t_def_ls *x; + + x = (t_def_ls *)pd_new(def_ls_class); + + x->x_ls_read = 0; +/* if(av[0].a_type == A_LONG){ + x->x_dimension= av[0].a_w.w_long; + x->x_ls_read = 1; + } else */ + if(av[0].a_type == A_FLOAT) { + x->x_dimension= (int) av[0].a_w.w_float; + x->x_ls_read = 1; + } else x->x_dimension= 0; + + if(x->x_dimension <2 || x->x_dimension >3){ + post("define-loudspeakers: Dimension has to be 2 or 3!",0); + return(0); + } + + + pointer = 1; + x->x_ls_amount= (ac-1) / (x->x_dimension - 1); + + /* read loudspeaker direction angles */ + for(i=0; i < x->x_ls_amount;i++){ +/* if(av[0].a_type == A_LONG) + x->x_ls[i].azi = (float) av[pointer].a_w.w_long; + else */ + if(av[0].a_type == A_FLOAT) + x->x_ls[i].azi = av[pointer].a_w.w_float; + else { + post("define-loudspeakers: Error in loudspeaker data!",0); + x->x_ls_read =0; + return; + } + pointer++; + if(x->x_dimension == 3){ /* 3-D */ +/* if(av[0].a_type == A_LONG) + x->x_ls[i].ele = (float) av[pointer].a_w.w_long; + else */ + if(av[0].a_type == A_FLOAT) + x->x_ls[i].ele = av[pointer].a_w.w_float; + else { + post("define-loudspeakers: Error in loudspeaker data!",0); + x->x_ls_read =0; + return; + } + pointer++; + } else + x->x_ls[i].ele = 0.0; /* in 2d elevation is zero */ + } + + if(x->x_ls_read == 1) + for(i=0;ix_ls_amount;i++) + ls_angles_to_cart(&x->x_ls[i]); + x->x_triplets_specified=0; + x->x_outlet0 = outlet_new(&x->x_ob, gensym("list")); /* create a (list) outlet */ + x->x_ls_set = NULL; + return(x); /* return a reference to the object instance */ +} + + + +void choose_ls_triplets(t_def_ls *x) + /* 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. + 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.*/ +{ + int i,j,k,l,m,li, table_size; + int *i_ptr; + t_ls vb1,vb2,tmp_vec; + int connections[MAX_LS_AMOUNT][MAX_LS_AMOUNT]; + float angles[MAX_LS_AMOUNT]; + int sorted_angles[MAX_LS_AMOUNT]; + float distance_table[((MAX_LS_AMOUNT * (MAX_LS_AMOUNT - 1)) / 2)]; + int distance_table_i[((MAX_LS_AMOUNT * (MAX_LS_AMOUNT - 1)) / 2)]; + int distance_table_j[((MAX_LS_AMOUNT * (MAX_LS_AMOUNT - 1)) / 2)]; + float distance; + t_ls_set *trip_ptr, *prev, *tmp_ptr; + int ls_amount = x->x_ls_amount; + t_ls *lss = x->x_ls; + if (ls_amount == 0) { + post("define-loudspeakers: Number of loudspeakers is zero",0); + return; + } + + for(i=0;ix_ls) > 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,x); + } + } + + /*calculate distancies between all lss and sorting them*/ + table_size =(((ls_amount - 1) * (ls_amount)) / 2); + for(i=0;i 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; jx_ls) == 1){ + connections[j][k] = 0; + connections[k][j] = 0; + } + } + } + + /* remove triangles which had crossing sides + with smaller triangles or include loudspeakers*/ + trip_ptr = x->x_ls_set; + prev = NULL; + while (trip_ptr != NULL){ + 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,x->x_ls,ls_amount) == 1 ){ + if(prev != NULL) { + prev->next = trip_ptr->next; + tmp_ptr = trip_ptr; + trip_ptr = trip_ptr->next; + freebytes(tmp_ptr, sizeof (struct t_ls_set)); + } else { + x->x_ls_set = trip_ptr->next; + tmp_ptr = trip_ptr; + trip_ptr = trip_ptr->next; + freebytes(tmp_ptr, sizeof (struct t_ls_set)); + } + } else { + prev = trip_ptr; + trip_ptr = trip_ptr->next; + + } + } + x->x_triplets_specified=1; +} + + +int any_ls_inside_triplet(int a, int b, int c,t_ls lss[MAX_LS_AMOUNT],int ls_amount) + /* returns 1 if there is loudspeaker(s) inside given ls triplet */ +{ + float invdet; + t_ls *lp1, *lp2, *lp3; + float invmx[9]; + int i,j,k; + float tmp; + int any_ls_inside, this_inside; + + lp1 = &(lss[a]); + lp2 = &(lss[b]); + lp3 = &(lss[c]); + + /* 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 = 0; + for(i=0; i< ls_amount; i++) { + if (i != a && i!=b && i != c){ + this_inside = 1; + for(j=0; j< 3; j++){ + tmp = lss[i].x * invmx[0 + j*3]; + tmp += lss[i].y * invmx[1 + j*3]; + tmp += lss[i].z * invmx[2 + j*3]; + if(tmp < -0.001) + this_inside = 0; + } + if(this_inside == 1) + any_ls_inside=1; + } + } + return any_ls_inside; +} + +void add_ldsp_triplet(int i, int j, int k, t_def_ls *x) + /* adds i,j,k triplet to structure*/ +{ + struct t_ls_set *trip_ptr, *prev; + trip_ptr = x->x_ls_set; + prev = NULL; + while (trip_ptr != NULL){ + prev = trip_ptr; + trip_ptr = trip_ptr->next; + } + trip_ptr = (struct t_ls_set*) + getbytes (sizeof (struct t_ls_set)); + if(prev == NULL) + x->x_ls_set = trip_ptr; + else + prev->next = trip_ptr; + trip_ptr->next = NULL; + trip_ptr->ls_nos[0] = i; + trip_ptr->ls_nos[1] = j; + trip_ptr->ls_nos[2] = k; +} + + + + +float vec_angle(t_ls v1, t_ls v2) +/* angle between two loudspeakers */ +{ + 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 fabs( acos( inner)); +} + +float vec_length(t_ls v1) +/* length of a vector */ +{ + return (sqrt(v1.x*v1.x + v1.y*v1.y + v1.z*v1.z)); +} + +float vec_prod(t_ls v1, t_ls v2) +/* vector dot product */ +{ + return (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); +} + + +float vol_p_side_lgth(int i, int j,int k, t_ls lss[MAX_LS_AMOUNT] ){ + /* 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; + t_ls xprod; + unq_cross_prod(lss[i], lss[j], &xprod); + volper = fabsf(vec_prod(xprod, lss[k])); + lgth = (fabsf(vec_angle(lss[i],lss[j])) + + fabsf(vec_angle(lss[i],lss[k])) + + fabsf(vec_angle(lss[j],lss[k]))); + if(lgth>0.00001) + return volper / lgth; + else + return 0.0; +} + +void unq_cross_prod(t_ls v1,t_ls v2, + t_ls *res) +/* vector cross product */ +{ + 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 lines_intersect(int i,int j,int k,int l,t_ls lss[MAX_LS_AMOUNT]) + /* checks if two lines intersect on 3D sphere + */ +{ + t_ls v1; + t_ls v2; + t_ls v3, neg_v3; + float angle; + float dist_ij,dist_kl,dist_iv3,dist_jv3,dist_inv3,dist_jnv3; + float dist_kv3,dist_lv3,dist_knv3,dist_lnv3; + + unq_cross_prod(lss[i],lss[j],&v1); + unq_cross_prod(lss[k],lss[l],&v2); + unq_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(lss[i],lss[j])); + dist_kl = (vec_angle(lss[k],lss[l])); + dist_iv3 = (vec_angle(lss[i],v3)); + dist_jv3 = (vec_angle(v3,lss[j])); + dist_inv3 = (vec_angle(lss[i],neg_v3)); + dist_jnv3 = (vec_angle(neg_v3,lss[j])); + dist_kv3 = (vec_angle(lss[k],v3)); + dist_lv3 = (vec_angle(v3,lss[l])); + dist_knv3 = (vec_angle(lss[k],neg_v3)); + dist_lnv3 = (vec_angle(neg_v3,lss[l])); + + /* 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 crossing point is on line between both loudspeakers return 1 */ + 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 calculate_3x3_matrixes(t_def_ls *x) + /* Calculates the inverse matrices for 3D */ +{ + float invdet; + t_ls *lp1, *lp2, *lp3; + float *invmx; + float *ptr; + struct t_ls_set *tr_ptr = x->x_ls_set; + int triplet_amount = 0, ftable_size,i,j,k, pointer,list_length=0; + t_atom *at; + t_ls *lss = x->x_ls; + + if (tr_ptr == NULL){ + post("define-loudspeakers: Not valid 3-D configuration\n",1); + return; + } + + /* counting triplet amount */ + while(tr_ptr != NULL){ + triplet_amount++; + tr_ptr = tr_ptr->next; + } + tr_ptr = x->x_ls_set; + list_length= triplet_amount * 21 + 2; /* was: + 3, pure data doesn't like errors on list_length */ + at= (t_atom *) getbytes(list_length*sizeof(t_atom)); + + SETFLOAT(&at[0], x->x_dimension); + SETFLOAT(&at[1], x->x_ls_amount); + pointer=2; + + while(tr_ptr != NULL){ + lp1 = &(lss[tr_ptr->ls_nos[0]]); + lp2 = &(lss[tr_ptr->ls_nos[1]]); + lp3 = &(lss[tr_ptr->ls_nos[2]]); + + /* 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; + for(i=0;i<3;i++){ + SETFLOAT(&at[pointer], tr_ptr->ls_nos[i]+1); + pointer++; + } + for(i=0;i<9;i++){ + SETFLOAT(&at[pointer], invmx[i]); + pointer++; + } + SETFLOAT(&at[pointer], lp1->x); pointer++; + SETFLOAT(&at[pointer], lp2->x); pointer++; + SETFLOAT(&at[pointer], lp3->x); pointer++; + SETFLOAT(&at[pointer], lp1->y); pointer++; + SETFLOAT(&at[pointer], lp2->y); pointer++; + SETFLOAT(&at[pointer], lp3->y); pointer++; + SETFLOAT(&at[pointer], lp1->z); pointer++; + SETFLOAT(&at[pointer], lp2->z); pointer++; + SETFLOAT(&at[pointer], lp3->z); pointer++; + + tr_ptr = tr_ptr->next; + } + outlet_anything(x->x_outlet0, gensym("loudspeaker-matrices"), list_length, at); + freebytes(at, list_length*sizeof(t_atom)); +} + + + +void choose_ls_tuplets(t_def_ls *x) + /* selects the loudspeaker pairs, calculates the inversion + matrices and stores the data to a global array*/ +{ + float atorad = (2 * 3.1415927 / 360) ; + int i,j,k; + float w1,w2; + float p1,p2; + int sorted_lss[MAX_LS_AMOUNT]; + int exist[MAX_LS_AMOUNT]; + int amount=0; + float inv_mat[MAX_LS_AMOUNT][4]; /* In 2-D ls amount == max amount of LS pairs */ + float *ptr; + float *ls_table; + t_ls *lss = x->x_ls; + long ls_amount=x->x_ls_amount; + long list_length; + t_atom *at; + long pointer; + + for(i=0;ix_ls,sorted_lss,ls_amount); + + /* adjacent loudspeakers are the loudspeaker pairs to be used.*/ + for(i=0;i<(ls_amount-1);i++){ + if((lss[sorted_lss[i+1]].azi - + lss[sorted_lss[i]].azi) <= (180 - 10)){ + if (calc_2D_inv_tmatrix( lss[sorted_lss[i]].azi, + lss[sorted_lss[i+1]].azi, + inv_mat[i]) != 0){ + exist[i]=1; + amount++; + } + } + } + + if(((6.283 - lss[sorted_lss[ls_amount-1]].azi) + +lss[sorted_lss[0]].azi) <= (180 - 10)) { + if(calc_2D_inv_tmatrix(lss[sorted_lss[ls_amount-1]].azi, + lss[sorted_lss[0]].azi, + inv_mat[ls_amount-1]) != 0) { + exist[ls_amount-1]=1; + amount++; + } + } + + + /* Output */ + list_length= amount * 6 + 2; + at= (t_atom *) getbytes(list_length*sizeof(t_atom)); + + SETFLOAT(&at[0], x->x_dimension); + SETFLOAT(&at[1], x->x_ls_amount); + pointer=2; + + for (i=0;ix_outlet0, gensym("loudspeaker-matrices"), list_length, at); + freebytes(at, list_length*sizeof(t_atom)); +} + +void sort_2D_lss(t_ls lss[MAX_LS_AMOUNT], int sorted_lss[MAX_LS_AMOUNT], + int ls_amount) +/* sort loudspeakers according to azimuth angle */ +{ + int i,j,index; + float tmp, tmp_azi; + float rad2ang = 360.0 / ( 2 * 3.141592 ); + + float x,y; + /* Transforming angles between -180 and 180 */ + for (i=0;i; +#X obj 102 281 sel 1; +#X obj 201 158 f; +#X obj 203 354 outlet; +#X obj 203 267 f; +#X obj 292 42 inlet; +#X text 339 42 reset to float; +#X text 148 44 test against high; +#X obj 102 91 t b f b f; +#X obj 292 89 t b f; +#X connect 0 0 10 0; +#X connect 1 0 2 1; +#X connect 1 0 6 1; +#X connect 2 0 3 0; +#X connect 3 0 4 0; +#X connect 4 0 1 0; +#X connect 6 0 5 0; +#X connect 7 0 11 0; +#X connect 10 0 6 0; +#X connect 10 1 2 0; +#X connect 10 2 1 0; +#X connect 10 3 4 1; +#X connect 11 0 6 0; +#X connect 11 1 1 0; diff --git a/makefile.irix b/makefile.irix new file mode 100644 index 0000000..6960439 --- /dev/null +++ b/makefile.irix @@ -0,0 +1,12 @@ +FLAGS = -DPD -DUNIX -DIRIX -DN32 -O2 -w +INCLUDE = -I../pd/src -I /usr/local/include + +all: vbap_irix6 definels_irix6 + +vbap_irix6: + cc $(FLAGS) $(INCLUDE) -o vbap.o -c vbap.c + ld -elf -shared -rdata_shared -o vbap.pd_irix6 vbap.o + +definels_irix6: + cc $(FLAGS) $(INCLUDE) -o define_loudspeakers.o -c define_loudspeakers.c + ld -elf -shared -rdata_shared -o define_loudspeakers.pd_irix6 define_loudspeakers.o diff --git a/makefile.nt b/makefile.nt new file mode 100644 index 0000000..bd0b6f3 --- /dev/null +++ b/makefile.nt @@ -0,0 +1,89 @@ +NAME=vbap +CSYM=vbap +NAMEB=define_loudspeakers +CSYMB=define_loudspeakers + +current: pd_nt + +# ----------------------- NT ----------------------- + +pd_nt: $(NAME).dll + +.SUFFIXES: .dll + +PDNTCFLAGS = /W3 /WX /O2 /G6 /DNT /DPD /nologo +VC="C:\Programme\Microsoft Visual Studio\VC98" + +PDNTINCLUDE = /I. /Ic:\pd\tcl\include /Ic:\pd\src /I$(VC)\include /Iinclude + +PDNTLDIR = $(VC)\Lib +PDNTLIB = $(PDNTLDIR)\libc.lib \ + $(PDNTLDIR)\oldnames.lib \ + $(PDNTLDIR)\kernel32.lib \ + $(PDNTLDIR)\user32.lib \ + $(PDNTLDIR)\uuid.lib \ + $(PDNTLDIR)\ws2_32.lib \ + c:\pd\bin\pd.lib \ + +.c.dll: + cl $(PDNTCFLAGS) $(PDNTINCLUDE) /c $(NAME).c + cl $(PDNTCFLAGS) $(PDNTINCLUDE) /c $(NAMEB).c + link /dll /export:$(CSYM)_setup $(NAME).obj $(PDNTLIB) + link /dll /export:$(CSYMB)_setup $(NAMEB).obj $(PDNTLIB) + +# ----------------------- IRIX 5.x ----------------------- + +pd_irix5: $(NAME).pd_irix5 + +.SUFFIXES: .pd_irix5 + +SGICFLAGS5 = -o32 -DPD -DUNIX -DIRIX -O2 + +SGIINCLUDE = -I../../src + +.c.pd_irix5: + cc $(SGICFLAGS5) $(SGIINCLUDE) -o $*.o -c $*.c + ld -elf -shared -rdata_shared -o $*.pd_irix5 $*.o + rm $*.o + +# ----------------------- IRIX 6.x ----------------------- + +pd_irix6: $(NAME).pd_irix6 + +.SUFFIXES: .pd_irix6 + +SGICFLAGS6 = -n32 -DPD -DUNIX -DIRIX -DN32 -woff 1080,1064,1185 \ + -OPT:roundoff=3 -OPT:IEEE_arithmetic=3 -OPT:cray_ivdep=true \ + -Ofast=ip32 + +.c.pd_irix6: + cc $(SGICFLAGS6) $(SGIINCLUDE) -o $*.o -c $*.c + ld -n32 -IPA -shared -rdata_shared -o $*.pd_irix6 $*.o + rm $*.o + +# ----------------------- LINUX i386 ----------------------- + +pd_linux: $(NAME).pd_linux + +.SUFFIXES: .pd_linux + +LINUXCFLAGS = -DPD -DUNIX -O2 -funroll-loops -fomit-frame-pointer \ + -Wall -W -Wshadow -Wstrict-prototypes -Werror \ + -Wno-unused -Wno-parentheses -Wno-switch + +LINUXINCLUDE = -I../../src + +.c.pd_linux: + cc $(LINUXCFLAGS) $(LINUXINCLUDE) -o $*.o -c $*.c + ld -export_dynamic -shared -o $*.pd_linux $*.o -lc -lm -L/usr/local/lib + strip --strip-unneeded $*.pd_linux + rm -f $*.o ../$*.pd_linux + ln -s $*/$*.pd_linux .. + +# ---------------------------------------------------------- + +install: + cp help-*.pd ../../doc/5.reference + +clean: + rm -f *.o *.pd_* so_locations diff --git a/playsample~.pd b/playsample~.pd new file mode 100644 index 0000000..bf2b06e --- /dev/null +++ b/playsample~.pd @@ -0,0 +1,56 @@ +#N canvas 27 300 849 403 10; +#X obj 38 354 table \$0music; +#X obj 148 160 soundfiler; +#X floatatom 148 183 5 0 0; +#X obj 243 207 phasor~ 1; +#X floatatom 266 181 8 0 0; +#X obj 243 237 *~ 0; +#X msg 148 137 read -resize \$1 \$2; +#X obj 199 83 symbol \$0music; +#X obj 148 110 pack s s; +#X obj 199 48 loadbang; +#X obj 12 13 inlet; +#X text 59 12 .aiff file name; +#X obj 311 15 inlet; +#X text 356 15 phasor speed; +#X obj 243 334 outlet~; +#X obj 243 267 tabread4~ \$0music; +#X obj 302 83 samplerate~; +#X floatatom 310 107 5 0 0; +#X obj 460 14 inlet; +#X text 505 14 gain; +#X obj 243 306 *~ 1; +#X obj 148 210 t b f; +#X obj 148 232 f; +#X obj 148 254 /; +#X obj 419 334 outlet~; +#X text 303 333 sample signal; +#X text 483 334 phasor signal; +#X msg 557 41 0; +#X obj 557 16 inlet; +#X text 601 16 restart; +#X connect 1 0 2 0; +#X connect 2 0 5 1; +#X connect 2 0 21 0; +#X connect 3 0 5 0; +#X connect 3 0 24 0; +#X connect 4 0 3 0; +#X connect 5 0 15 0; +#X connect 6 0 1 0; +#X connect 7 0 8 1; +#X connect 8 0 6 0; +#X connect 9 0 7 0; +#X connect 9 0 16 0; +#X connect 10 0 8 0; +#X connect 12 0 3 0; +#X connect 15 0 20 0; +#X connect 16 0 17 0; +#X connect 16 0 22 1; +#X connect 18 0 20 1; +#X connect 20 0 14 0; +#X connect 21 0 22 0; +#X connect 21 1 23 1; +#X connect 22 0 23 0; +#X connect 23 0 4 0; +#X connect 27 0 3 1; +#X connect 28 0 27 0; diff --git a/recent.pd b/recent.pd new file mode 100644 index 0000000..add6c7d --- /dev/null +++ b/recent.pd @@ -0,0 +1,14 @@ +#N canvas 399 148 452 302 12; +#X obj 56 55 inlet; +#X msg 91 123 clear; +#X obj 59 252 outlet; +#X obj 166 54 inlet; +#X text 8 5 output only most "recent" messages; +#X obj 56 89 t a b; +#X obj 59 202 pipe \$1; +#X connect 0 0 5 0; +#X connect 1 0 6 0; +#X connect 3 0 6 1; +#X connect 5 0 6 0; +#X connect 5 1 1 0; +#X connect 6 0 2 0; diff --git a/so_locations b/so_locations new file mode 100644 index 0000000..0edc062 --- /dev/null +++ b/so_locations @@ -0,0 +1,6 @@ +vbap.pd_irix6 \ + :st = .text 0x5ffe0000, 0x00010000:\ + :st = .data 0x5fff0000, 0x00010000: +define_loudspeakers.pd_irix6 \ + :st = .text 0x5ffc0000, 0x00010000:\ + :st = .data 0x5ffd0000, 0x00010000: diff --git a/vbap-demo.pd b/vbap-demo.pd new file mode 100644 index 0000000..027840a --- /dev/null +++ b/vbap-demo.pd @@ -0,0 +1,41 @@ +#N canvas 11 10 871 352 10; +#X obj 49 298 vbap.main; +#X obj 50 80 playsample~; +#X obj 50 55 openpanel; +#X msg 50 31 bang; +#X text 96 30 click to load and play a sample; +#X floatatom 133 80 5 0 0; +#X floatatom 133 101 5 0 0; +#X text 182 80 pitch; +#X text 182 102 volume; +#X floatatom 76 142 5 0 0; +#X floatatom 122 142 5 0 0; +#X floatatom 168 142 5 0 0; +#X text 218 143 set azimuth \, elevation \, spread; +#N canvas 152 420 615 353 using 0; +#X obj 59 258 graph-to-aziele; +#X obj 59 233 pack f f; +#X floatatom 59 281 5 0 0; +#X obj 59 78 grid grid1 144 0 1 144 0 1 0 0.001 0.001 10 10 103 91 +; +#X floatatom 159 281 5 0 0; +#X text 21 17 To use Yves Degoyon's GRID (http://ydegoyon.free.fr) +use graph-to-aziele.pd to count the azimuth and elevation.; +#X text 25 49 (note: GRID must output values between 0 - 1); +#X connect 0 0 2 0; +#X connect 0 1 4 0; +#X connect 1 0 0 0; +#X connect 3 0 1 0; +#X connect 3 1 1 1; +#X restore 506 112 pd using GRID with vbap; +#X text 130 292 define loudspeakers \, receive signals and data from +vbap \, output audio; +#X obj 50 174 vbapmodule 1; +#X connect 1 0 15 0; +#X connect 2 0 1 0; +#X connect 3 0 2 0; +#X connect 5 0 1 1; +#X connect 6 0 1 2; +#X connect 9 0 15 1; +#X connect 10 0 15 2; +#X connect 11 0 15 3; diff --git a/vbap-help.pd b/vbap-help.pd new file mode 100644 index 0000000..7a1f979 --- /dev/null +++ b/vbap-help.pd @@ -0,0 +1,52 @@ +#N canvas 91 360 949 493 10; +#X obj 112 257 vbap 0 0; +#X obj 134 319 print; +#X obj 62 113 define_loudspeakers 3 -45 0 45 0 0 45 180 45; +#X msg 62 87 bang; +#X text 442 79 1 Use define_loudspeakers to list the speaker positions. +The example here defines loudspeakers in three dimensions (the first +parameter). For each speaker \, define its azimuth and elevation. Here +we have speakers front left and right with no elevation (-45 0 45 0) +and front and back with 45 degrees of elevation (0 45 180 45). Send +the data to vbap.; +#X floatatom 129 223 5 0 0; +#X floatatom 173 223 5 0 0; +#X floatatom 217 223 5 0 0; +#X msg 112 179 bang; +#X obj 112 356 route 0 1 2 3; +#X floatatom 112 415 10 0 0; +#X floatatom 191 415 10 0 0; +#X floatatom 270 415 10 0 0; +#X floatatom 349 415 10 0 0; +#X text 442 169 In two dimensions \, only specify the azimuth. (for +example "define_loudspeakers 2 -45 45 0 180"; +#X text 151 201 azimuth \, elevation and spread; +#X text 444 352 For an example of how to use vbap with matrix~ from +zexy-library \, see vbap-demo.pd.; +#X text 63 21 VBAP and define_loudspeakers; +#X text 444 300 The spread-parameter can be used to prevent a situation +where sound is coming from one speaker only \, which would make speaker +positions "visible". The range is 0 to 100; +#X floatatom 152 286 5 0 0; +#X floatatom 196 286 5 0 0; +#X floatatom 240 286 5 0 0; +#X text 178 306 actual location; +#X text 444 222 2 For vbap \, give azimuth and elevation for the desired +location. Bang the first inlet and vbap will output gain-factors for +each speaker and the actual location produced. This can be different +from the desired one depending where your speakers are.; +#X connect 0 0 1 0; +#X connect 0 0 9 0; +#X connect 0 1 19 0; +#X connect 0 2 20 0; +#X connect 0 3 21 0; +#X connect 2 0 0 0; +#X connect 3 0 2 0; +#X connect 5 0 0 1; +#X connect 6 0 0 2; +#X connect 7 0 0 3; +#X connect 8 0 0 0; +#X connect 9 0 10 0; +#X connect 9 1 11 0; +#X connect 9 2 12 0; +#X connect 9 3 13 0; diff --git a/vbap.c b/vbap.c new file mode 100644 index 0000000..507ef39 --- /dev/null +++ b/vbap.c @@ -0,0 +1,650 @@ +/* vbap.c vers 0.99 for max4.0 + +written by Ville Pulkki 1999-2001 +Helsinki University of Technology +and +Unversity of California at Berkeley + +See copyright in file with name COPYRIGHT */ + +#ifdef NT +#define sqrtf sqrt +#endif + +#include +#include "m_pd.h" /* you must include this - it contains the external object's link to pure data */ + +#define RES_ID 9171 /* resource ID for assistance (we'll add that later) */ +#define MAX_LS_SETS 100 /* maximum number of loudspeaker sets (triplets or pairs) allowed */ +#define MAX_LS_AMOUNT 55 /* maximum amount of loudspeakers, can be increased */ + +typedef struct vbap /* This defines the object as an entity made up of other things */ +{ + t_object x_ob; + t_float x_azi; /* panning direction azimuth */ + t_float x_ele; /* panning direction elevation */ + void *x_outlet0; /* outlet creation - inlets are automatic */ + void *x_outlet1; + void *x_outlet2; + void *x_outlet3; + float x_set_inv_matx[MAX_LS_SETS][9]; /* inverse matrice for each loudspeaker set */ + float x_set_matx[MAX_LS_SETS][9]; /* matrice for each loudspeaker set */ + long x_lsset[MAX_LS_SETS][3]; /* channel numbers of loudspeakers in each LS set */ + long x_lsset_available; /* have loudspeaker sets been defined with define_loudspeakers */ + long x_lsset_amount; /* amount of loudspeaker sets */ + long x_ls_amount; /* amount of loudspeakers */ + long x_dimension; /* 2 or 3 */ + t_float x_spread; /* speading amount of virtual source (0-100) */ + float x_spread_base[3]; /* used to create uniform spreading */ +} t_vbap; + +/* Globals */ + +void new_spread_dir(t_vbap *x, float spreaddir[3], float vscartdir[3], float spread_base[3]); +void new_spread_base(t_vbap *x, float spreaddir[3], float vscartdir[3]); +static t_class *vbap_class; +void cross_prod(float v1[3], float v2[3], + float v3[3]); +void additive_vbap(float *final_gs, float cartdir[3], t_vbap *x); +void vbap_bang(t_vbap *x); +void vbap_int(t_vbap *x, t_float n); +void vbap_matrix(t_vbap *x, t_symbol *s, int ac, t_atom *av); +void vbap_in1(t_vbap *x, long n); +void vbap_in2(t_vbap *x, long n); +void vbap_in3(t_vbap *x, long n); +void spread_it(t_vbap *x, float *final_gs); +static void *vbap_new(t_symbol *s, int ac, t_atom *av); /* using A_GIMME - typed message list */ +void vbap(float g[3], long ls[3], t_vbap *x); +void angle_to_cart(long azi, long ele, float res[3]); +void cart_to_angle(float cvec[3], float avec[3]); + +/* above are the prototypes for the methods/procedures/functions you will use */ + +void vbap_setup(void) +{ + vbap_class = class_new(gensym("vbap"), (t_newmethod)vbap_new, 0, (short)sizeof(t_vbap), 0, A_GIMME, 0); + /* vbap_new = creation function, A_DEFLONG = its (optional) arguement is a long (32-bit) int */ + +/* max methods ... */ +/* +/* addbang((method)vbap_bang); /* the procedure it uses when it gets a bang in the left inlet */ +/* addint((method)vbap_int); /* the rocedure for an int in the left inlet (inlet 0) */ +/* addinx((method)vbap_in1, 1); /* the rocedure for an int in the right inlet (inlet 1) */ +/* addinx((method)vbap_in2, 2); /* the rocedure for an int in the right inlet (inlet 2) */ +/* addinx((method)vbap_in3, 3); +/* addmess((method)vbap_matrix, "loudspeaker-matrices", A_GIMME, 0); */ + +/* pure data: */ + + class_addbang(vbap_class, vbap_bang); + class_addfloat(vbap_class, vbap_int); + class_addmethod(vbap_class, (t_method)vbap_matrix, gensym("loudspeaker-matrices"), A_GIMME, 0); +} + + +void angle_to_cart(long azi, long ele, float res[3]) +/* converts angular coordinates to cartesian */ +{ + float atorad = (2 * 3.1415927 / 360) ; + res[0] = cos((float) azi * atorad) * cos((float) ele * atorad); + res[1] = sin((float) azi * atorad) * cos((float) ele * atorad); + res[2] = sin((float) ele * atorad); +} + +void cart_to_angle(float cvec[3], float avec[3]) +/* converts cartesian coordinates to angular */ +{ + float tmp, tmp2, tmp3, tmp4; + float atorad = (2 * 3.1415927 / 360) ; + float pi = 3.1415927; + float power; + float dist, atan_y_per_x, atan_x_pl_y_per_z; + float azi, ele; + + if(cvec[0]==0.0) + atan_y_per_x = pi / 2; + else + atan_y_per_x = atan(cvec[1] / cvec[0]); + azi = atan_y_per_x / atorad; + if(cvec[0]<0.0) + azi +=180; + dist = sqrt(cvec[0]*cvec[0] + cvec[1]*cvec[1]); + if(cvec[2]==0.0) + atan_x_pl_y_per_z = 0.0; + else + atan_x_pl_y_per_z = atan(cvec[2] / dist); + if(dist == 0.0) + if(cvec[2]<0.0) + atan_x_pl_y_per_z = -pi/2.0; + else + atan_x_pl_y_per_z = pi/2.0; + ele = atan_x_pl_y_per_z / atorad; + dist = sqrtf(cvec[0] * cvec[0] +cvec[1] * cvec[1] +cvec[2]*cvec[2]); + avec[0]=azi; + avec[1]=ele; + avec[2]=dist; +} + + +void vbap(float g[3], long ls[3], t_vbap *x) +{ + /* calculates gain factors using loudspeaker setup and given direction */ + float power; + int i,j,k, gains_modified; + float small_g; + float big_sm_g, gtmp[3]; + long winner_set; + float cartdir[3]; + float new_cartdir[3]; + float new_angle_dir[3]; + long dim = x->x_dimension; + long neg_g_am, best_neg_g_am; + + /* transfering the azimuth angle to a decent value */ + while(x->x_azi > 180) + x->x_azi -= 360; + while(x->x_azi < -179) + x->x_azi += 360; + + /* transferring the elevation to a decent value */ + if(dim == 3){ + while(x->x_ele > 180) + x->x_ele -= 360; + while(x->x_ele < -179) + x->x_ele += 360; + } else + x->x_ele = 0; + + + /* go through all defined loudspeaker sets and find the set which + // has all positive values. If such is not found, set with largest + // minimum value is chosen. If at least one of gain factors of one LS set is negative + // it means that the virtual source does not lie in that LS set. */ + + angle_to_cart(x->x_azi,x->x_ele,cartdir); + big_sm_g = -100000.0; /* initial value for largest minimum gain value */ + best_neg_g_am=3; /* how many negative values in this set */ + + + for(i=0;ix_lsset_amount;i++){ + small_g = 10000000.0; + neg_g_am = 3; + for(j=0;jx_set_inv_matx[i][k+j*dim]; + if(gtmp[j] < small_g) + small_g = gtmp[j]; + if(gtmp[j]>= -0.01) + neg_g_am--; + } + if(small_g > big_sm_g && neg_g_am <= best_neg_g_am){ + big_sm_g = small_g; + best_neg_g_am = neg_g_am; + winner_set=i; + g[0]=gtmp[0]; g[1]=gtmp[1]; + ls[0]= x->x_lsset[i][0]; ls[1]= x->x_lsset[i][1]; + if(dim==3){ + g[2]=gtmp[2]; + ls[2]= x->x_lsset[i][2]; + } else { + g[2]=0.0; + ls[2]=0; + } + } + } + + /* If chosen set produced a negative value, make it zero and + // calculate direction that corresponds to these new + // gain values. This happens when the virtual source is outside of + // all loudspeaker sets. */ + + if(dim==3){ + gains_modified=0; + for(i=0;ix_set_matx[winner_set][0] * g[0] + + x->x_set_matx[winner_set][1] * g[1] + + x->x_set_matx[winner_set][2] * g[2]; + new_cartdir[1] = x->x_set_matx[winner_set][3] * g[0] + + x->x_set_matx[winner_set][4] * g[1] + + x->x_set_matx[winner_set][5] * g[2]; + new_cartdir[2] = x->x_set_matx[winner_set][6] * g[0] + + x->x_set_matx[winner_set][7] * g[1] + + x->x_set_matx[winner_set][8] * g[2]; + cart_to_angle(new_cartdir,new_angle_dir); + x->x_azi = (long) (new_angle_dir[0] + 0.5); + x->x_ele = (long) (new_angle_dir[1] + 0.5); + } + } + + power=sqrt(g[0]*g[0] + g[1]*g[1] + g[2]*g[2]); + g[0] /= power; + g[1] /= power; + g[2] /= power; +} + + +void cross_prod(float v1[3], float v2[3], + float v3[3]) +/* vector cross product */ +{ + float length; + v3[0] = (v1[1] * v2[2] ) - (v1[2] * v2[1]); + v3[1] = (v1[2] * v2[0] ) - (v1[0] * v2[2]); + v3[2] = (v1[0] * v2[1] ) - (v1[1] * v2[0]); + + length= sqrt(v3[0]*v3[0] + v3[1]*v3[1] + v3[2]*v3[2]); + v3[0] /= length; + v3[1] /= length; + v3[2] /= length; +} + +void additive_vbap(float *final_gs, float cartdir[3], t_vbap *x) +/* calculates gains to be added to previous gains, used in +// multiple direction panning (source spreading) */ +{ + float power; + int i,j,k, gains_modified; + float small_g; + float big_sm_g, gtmp[3]; + long winner_set; + float new_cartdir[3]; + float new_angle_dir[3]; + long dim = x->x_dimension; + long neg_g_am, best_neg_g_am; + float g[3]; + long ls[3]; + + big_sm_g = -100000.0; + best_neg_g_am=3; + + for(i=0;ix_lsset_amount;i++){ + small_g = 10000000.0; + neg_g_am = 3; + for(j=0;jx_set_inv_matx[i][k+j*dim]; + if(gtmp[j] < small_g) + small_g = gtmp[j]; + if(gtmp[j]>= -0.01) + neg_g_am--; + } + if(small_g > big_sm_g && neg_g_am <= best_neg_g_am){ + big_sm_g = small_g; + best_neg_g_am = neg_g_am; + winner_set=i; + g[0]=gtmp[0]; g[1]=gtmp[1]; + ls[0]= x->x_lsset[i][0]; ls[1]= x->x_lsset[i][1]; + if(dim==3){ + g[2]=gtmp[2]; + ls[2]= x->x_lsset[i][2]; + } else { + g[2]=0.0; + ls[2]=0; + } + } + } + + gains_modified=0; + for(i=0;ix_azi+90, 0, spread_base); + gamma = acos(vscartdir[0] * spread_base[0] + + vscartdir[1] * spread_base[1] + + vscartdir[2] * spread_base[2])/pi*180; + } + beta = 180 - gamma; + b=sin(x->x_spread * pi / 180) / sin(beta * pi / 180); + a=sin((180- x->x_spread - beta) * pi / 180) / sin (beta * pi / 180); + spreaddir[0] = a * vscartdir[0] + b * spread_base[0]; + spreaddir[1] = a * vscartdir[1] + b * spread_base[1]; + spreaddir[2] = a * vscartdir[2] + b * spread_base[2]; + + power=sqrt(spreaddir[0]*spreaddir[0] + spreaddir[1]*spreaddir[1] + + spreaddir[2]*spreaddir[2]); + spreaddir[0] /= power; + spreaddir[1] /= power; + spreaddir[2] /= power; +} + +void new_spread_base(t_vbap *x, float spreaddir[3], float vscartdir[3]) +/* subroutine for spreading */ +{ + float d; + float pi = 3.1415927; + float power; + + d = cos(x->x_spread/180*pi); + x->x_spread_base[0] = spreaddir[0] - d * vscartdir[0]; + x->x_spread_base[1] = spreaddir[1] - d * vscartdir[1]; + x->x_spread_base[2] = spreaddir[2] - d * vscartdir[2]; + power=sqrt(x->x_spread_base[0]*x->x_spread_base[0] + x->x_spread_base[1]*x->x_spread_base[1] + + x->x_spread_base[2]*x->x_spread_base[2]); + x->x_spread_base[0] /= power; + x->x_spread_base[1] /= power; + x->x_spread_base[2] /= power; +} + +void spread_it(t_vbap *x, float *final_gs) +/* +// apply the sound signal to multiple panning directions +// that causes some spreading. +// See theory in paper V. Pulkki "Uniform spreading of amplitude panned +// virtual sources" in WASPAA 99 +*/ +{ + float vscartdir[3]; + float spreaddir[16][3]; + float spreadbase[16][3]; + long i, spreaddirnum; + float power; + if(x->x_dimension == 3){ + spreaddirnum=16; + angle_to_cart(x->x_azi,x->x_ele,vscartdir); + new_spread_dir(x, spreaddir[0], vscartdir, x->x_spread_base); + new_spread_base(x, spreaddir[0], vscartdir); + cross_prod(x->x_spread_base, vscartdir, spreadbase[1]); /* four orthogonal dirs */ + cross_prod(spreadbase[1], vscartdir, spreadbase[2]); + cross_prod(spreadbase[2], vscartdir, spreadbase[3]); + + /* four between them */ + for(i=0;i<3;i++) spreadbase[4][i] = (x->x_spread_base[i] + spreadbase[1][i]) / 2.0; + for(i=0;i<3;i++) spreadbase[5][i] = (spreadbase[1][i] + spreadbase[2][i]) / 2.0; + for(i=0;i<3;i++) spreadbase[6][i] = (spreadbase[2][i] + spreadbase[3][i]) / 2.0; + for(i=0;i<3;i++) spreadbase[7][i] = (spreadbase[3][i] + x->x_spread_base[i]) / 2.0; + + /* four at half spreadangle */ + for(i=0;i<3;i++) spreadbase[8][i] = (vscartdir[i] + x->x_spread_base[i]) / 2.0; + for(i=0;i<3;i++) spreadbase[9][i] = (vscartdir[i] + spreadbase[1][i]) / 2.0; + for(i=0;i<3;i++) spreadbase[10][i] = (vscartdir[i] + spreadbase[2][i]) / 2.0; + for(i=0;i<3;i++) spreadbase[11][i] = (vscartdir[i] + spreadbase[3][i]) / 2.0; + + /* four at quarter spreadangle */ + for(i=0;i<3;i++) spreadbase[12][i] = (vscartdir[i] + spreadbase[8][i]) / 2.0; + for(i=0;i<3;i++) spreadbase[13][i] = (vscartdir[i] + spreadbase[9][i]) / 2.0; + for(i=0;i<3;i++) spreadbase[14][i] = (vscartdir[i] + spreadbase[10][i]) / 2.0; + for(i=0;i<3;i++) spreadbase[15][i] = (vscartdir[i] + spreadbase[11][i]) / 2.0; + + additive_vbap(final_gs,spreaddir[0],x); + for(i=1;ix_dimension == 2) { + spreaddirnum=6; + + angle_to_cart(x->x_azi - x->x_spread, 0, spreaddir[0]); + angle_to_cart(x->x_azi - x->x_spread/2, 0, spreaddir[1]); + angle_to_cart(x->x_azi - x->x_spread/4, 0, spreaddir[2]); + angle_to_cart(x->x_azi + x->x_spread/4, 0, spreaddir[3]); + angle_to_cart(x->x_azi + x->x_spread/2, 0, spreaddir[4]); + angle_to_cart(x->x_azi + x->x_spread, 0, spreaddir[5]); + + for(i=0;ix_spread > 70) + for(i=0;ix_ls_amount;i++){ + final_gs[i] += (x->x_spread - 70) / 30.0 * (x->x_spread - 70) / 30.0 * 10.0; + } + + for(i=0,power=0.0;ix_ls_amount;i++){ + power += final_gs[i] * final_gs[i]; + } + + power = sqrt(power); + for(i=0;ix_ls_amount;i++){ + final_gs[i] /= power; + } +} + + +void vbap_bang(t_vbap *x) +/* top level, vbap gains are calculated and outputted */ +{ + t_atom at[MAX_LS_AMOUNT]; + float g[3]; + long ls[3]; + long i; + float *final_gs; + + final_gs = (float *) getbytes(x->x_ls_amount * sizeof(float)); + if(x->x_lsset_available ==1){ + vbap(g,ls, x); + for(i=0;ix_ls_amount;i++) + final_gs[i]=0.0; + for(i=0;ix_dimension;i++){ + final_gs[ls[i]-1]=g[i]; + } + if(x->x_spread != 0){ + spread_it(x,final_gs); + } + for(i=0;ix_ls_amount;i++) { + SETFLOAT(&at[0], (t_float)i); + SETFLOAT(&at[1], (t_float)final_gs[i]); + outlet_list(x->x_outlet0, gensym("list") /* was: 0L */, 2, at); + } + outlet_float(x->x_outlet1, x->x_azi); + outlet_float(x->x_outlet2, x->x_ele); + outlet_float(x->x_outlet3, x->x_spread); + } + else + post("vbap: Configure loudspeakers first!",0); +/* freebytes(final_gs, x->x_ls_amount * sizeof(float)); /* bug fix added 9/00 */ +} + +/*--------------------------------------------------------------------------*/ + +void vbap_int(t_vbap *x, t_float n) /* x = the instance of the object, n = the int received in the right inlet */ +{ + /* do something if an int comes in the left inlet??? */ +} + +void vbap_matrix(t_vbap *x, t_symbol *s, int ac, t_atom *av) +/* read in loudspeaker matrices */ +{ + long counter; + long datapointer=0; + long setpointer=0; + long i; + long deb=0; + + if(ac>0) +/* if(av[datapointer].a_type == A_LONG){ + x->x_dimension = av[datapointer++].a_w.w_long; + x->x_lsset_available=1; + } else */ + if(av[datapointer].a_type == A_FLOAT){ + x->x_dimension = (long) av[datapointer++].a_w.w_float; + x->x_lsset_available=1; + } else { + post("Error in loudspeaker data!",0); + x->x_lsset_available=0; + return; + } +/* post("%d",deb++); */ + if(ac>1) +/* if(av[datapointer].a_type == A_LONG) + x->x_ls_amount = av[datapointer++].a_w.w_long; + else */ + if(av[datapointer].a_type == A_FLOAT) + x->x_ls_amount = (long) av[datapointer++].a_w.w_float; + else { + post("vbap: Error in loudspeaker data!",0); + x->x_lsset_available=0; + return; + } + else + x->x_lsset_available=0; + +/* post("%d",deb++); */ + if(x->x_dimension == 3) + counter = (ac - 2) / ((x->x_dimension * x->x_dimension*2) + x->x_dimension); + if(x->x_dimension == 2) + counter = (ac - 2) / ((x->x_dimension * x->x_dimension) + x->x_dimension); + x->x_lsset_amount=counter; + + if(counter<=0){ + post("vbap: Error in loudspeaker data!",0); + x->x_lsset_available=0; + return; + } + + + while(counter-- > 0){ + for(i=0; i < x->x_dimension; i++){ + if(av[datapointer].a_type == A_FLOAT){ + x->x_lsset[setpointer][i]=(long)av[datapointer++].a_w.w_float; +/* post("%d",deb++); */ + } + else{ + post("vbap: Error in loudspeaker data!",0); + x->x_lsset_available=0; + return; + } + } + + for(i=0; i < x->x_dimension*x->x_dimension; i++){ + if(av[datapointer].a_type == A_FLOAT){ + x->x_set_inv_matx[setpointer][i]=av[datapointer++].a_w.w_float; +/* post("%d",deb++); */ + } + else { + post("vbap: Error in loudspeaker data!",0); + x->x_lsset_available=0; + return; + } + } + if(x->x_dimension == 3){ + for(i=0; i < x->x_dimension*x->x_dimension; i++){ + if(av[datapointer].a_type == A_FLOAT){ + x->x_set_matx[setpointer][i]=av[datapointer++].a_w.w_float; +/* post("%d",deb++); */ + } + else { + post("vbap: Error in loudspeaker data!",0); + x->x_lsset_available=0; + return; + } + } + } + + setpointer++; + } + post("vbap: Loudspeaker setup configured!",0); +} + +void vbap_in1(t_vbap *x, long n) /* x = the instance of the object, n = the int received in the right inlet */ +/* panning angle azimuth */ +{ + x->x_azi = n; /* store n in a global variable */ + +} + +void vbap_in2(t_vbap *x, long n) /* x = the instance of the object, n = the int received in the right inlet */ +/* panning angle elevation */ +{ + x->x_ele = n; /* store n in a global variable */ + +} +/*--------------------------------------------------------------------------*/ + +void vbap_in3(t_vbap *x, long n) /* x = the instance of the object, n = the int received in the right inlet */ +/* spread amount */ +{ + if (n<0) n = 0; + if (n>100) n = 100; + x->x_spread = n; /* store n in a global variable */ + +} + + +static void *vbap_new(t_symbol *s, int ac, t_atom *av) +/* create new instance of object... MUST send it an int even if you do nothing with this int!! */ +{ + t_vbap *x; + x = (t_vbap *)pd_new(vbap_class); + +/* MAX: +/* intin(x,3); +/* intin(x,2); /* create a second (int) inlet... remember right-to-left ordering in Max */ +/* intin(x,1); /* create a second (int) inlet... remember right-to-left ordering in Max */ +/* x->x_outlet3 = intout(x); +/* x->x_outlet2 = intout(x); /* create an (int) outlet - rightmost outlet first... */ +/* x->x_outlet1 = intout(x); /* create an (int) outlet */ +/* x->x_outlet0 = listout(x); /* create a (list) outlet */ +/* +/* pure data: */ + + floatinlet_new(&x->x_ob, &x->x_azi); + floatinlet_new(&x->x_ob, &x->x_ele); + floatinlet_new(&x->x_ob, &x->x_spread); + + x->x_outlet0 = outlet_new(&x->x_ob, gensym("list")); + x->x_outlet1 = outlet_new(&x->x_ob, gensym("float")); + x->x_outlet2 = outlet_new(&x->x_ob, gensym("float")); + x->x_outlet3 = outlet_new(&x->x_ob, gensym("float")); + +/* - */ + + + x->x_azi = 0; + x->x_ele = 0; + x->x_spread_base[0] = 0.0; + x->x_spread_base[1] = 1.0; + x->x_spread_base[2] = 0.0; + x->x_spread = 0; + x->x_lsset_available =0; + if (ac>0) { +/* if (av[0].a_type == A_LONG) + x->x_azi = av[0].a_w.w_long; + else */ + if (av[0].a_type == A_FLOAT) + x->x_azi = (long)av[0].a_w.w_float; + } + if (ac>1) { +/* if (av[1].a_type == A_LONG) + x->x_ele = av[1].a_w.w_long; + else */ + if (av[1].a_type == A_FLOAT) + x->x_ele = (long)av[1].a_w.w_float; + } + return(x); /* return a reference to the object instance */ +} + diff --git a/vbap.dll b/vbap.dll new file mode 100644 index 0000000..1ee6214 Binary files /dev/null and b/vbap.dll differ diff --git a/vbap.main.pd b/vbap.main.pd new file mode 100644 index 0000000..e4c11b4 --- /dev/null +++ b/vbap.main.pd @@ -0,0 +1,92 @@ +#N canvas 153 122 664 665 10; +#X obj 22 149 bng 15 250 50 0 empty empty empty 20 8 32 8 -262144 -1 +-1; +#X floatatom 353 470 5 0 0; +#X obj 166 579 dac~ 1 2 3 4 5 6 7 8; +#X obj 109 535 *~ 1; +#X obj 142 535 *~ 1; +#X obj 175 535 *~ 1; +#X obj 207 535 *~ 1; +#X obj 239 535 *~ 1; +#X obj 271 535 *~ 1; +#X obj 304 535 *~ 1; +#X obj 336 535 *~ 1; +#X floatatom 382 504 5 0 0; +#X text 401 470 crossfade; +#X obj 22 208 send speaker_setup; +#X obj 22 169 define_loudspeakers 3 -30 0 30 0 -90 0 90 0 180 0 180 +45 -45 45 45 45; +#X obj 326 443 matrix 4 8; +#X obj 269 470 matrix~ 4 8; +#X obj 39 146 loadbang; +#X msg 471 169 \; pd dsp 1; +#X text 540 175 turn on audio; +#X text 430 504 gain; +#X obj 467 496 vsl 15 128 0 1 0 0 empty empty empty 20 8 32 8 -262144 +-1 -1 0 1; +#X msg 422 452 50; +#X obj 438 413 loadbang; +#X obj 484 494 r master; +#X obj 21 89 inlet; +#X text 66 89 no meaning; +#X obj 326 340 receive matrix; +#X text 30 22 define loudspeakers \, receive signals and data from +vbap \, output audio; +#X obj 99 146 receive define_ls; +#X obj 52 318 catch~ 1chan; +#X obj 162 318 catch~ 2chan; +#X obj 272 318 catch~ 3chan; +#X obj 382 318 catch~ 4chan; +#N canvas 229 245 540 236 parameters.readme 0; +#X text 37 35 define_loudspeakers +<..>; +#X text 37 70 dimensions is 2 or 3 \, followed by list of azimuths +(in 2d) or pairs (in 3d) \, defining the number +and positions of loudspeakers.; +#X text 38 122 azimuth is -180 to 180 \, where -90 is left \, 0 front +\, 90 right and 180 back.; +#X text 38 161 elevation is -90 to 90 \, where -90 is down \, 0 is +not elevated and 90 is up.; +#X restore 258 206 pd parameters.readme; +#X connect 0 0 14 0; +#X connect 1 0 16 5; +#X connect 3 0 2 0; +#X connect 4 0 2 1; +#X connect 5 0 2 2; +#X connect 6 0 2 3; +#X connect 7 0 2 4; +#X connect 8 0 2 5; +#X connect 9 0 2 6; +#X connect 10 0 2 7; +#X connect 11 0 10 1; +#X connect 11 0 9 1; +#X connect 11 0 8 1; +#X connect 11 0 7 1; +#X connect 11 0 6 1; +#X connect 11 0 5 1; +#X connect 11 0 4 1; +#X connect 11 0 3 1; +#X connect 11 0 9 1; +#X connect 11 0 10 1; +#X connect 14 0 13 0; +#X connect 15 0 16 4; +#X connect 16 0 3 0; +#X connect 16 1 4 0; +#X connect 16 2 5 0; +#X connect 16 3 6 0; +#X connect 16 4 7 0; +#X connect 16 5 8 0; +#X connect 16 6 9 0; +#X connect 16 7 10 0; +#X connect 17 0 0 0; +#X connect 21 0 11 0; +#X connect 22 0 1 0; +#X connect 23 0 22 0; +#X connect 23 0 18 0; +#X connect 24 0 21 0; +#X connect 27 0 15 0; +#X connect 29 0 0 0; +#X connect 30 0 16 0; +#X connect 31 0 16 1; +#X connect 32 0 16 2; +#X connect 33 0 16 3; diff --git a/vbap.pd_irix6 b/vbap.pd_irix6 new file mode 100755 index 0000000..17866f9 Binary files /dev/null and b/vbap.pd_irix6 differ diff --git a/vbapmodule.pd b/vbapmodule.pd new file mode 100644 index 0000000..01484a8 --- /dev/null +++ b/vbapmodule.pd @@ -0,0 +1,22 @@ +#N canvas 94 380 450 300 10; +#X obj 30 44 inlet~; +#X obj 62 158 send matrix; +#X obj 46 183 throw~ \$1chan; +#X floatatom 180 186 5 0 0; +#X floatatom 225 186 5 0 0; +#X floatatom 270 186 5 0 0; +#X text 137 217 actual azi / ele / spread; +#X obj 82 44 inlet; +#X obj 122 44 inlet; +#X obj 162 44 inlet; +#X text 210 44 azi / ele / spread; +#X obj 46 112 vbapsnd \$1; +#X connect 0 0 11 0; +#X connect 7 0 11 1; +#X connect 8 0 11 2; +#X connect 9 0 11 3; +#X connect 11 0 2 0; +#X connect 11 1 1 0; +#X connect 11 2 3 0; +#X connect 11 3 4 0; +#X connect 11 4 5 0; diff --git a/vbapsnd.pd b/vbapsnd.pd new file mode 100644 index 0000000..e73f027 --- /dev/null +++ b/vbapsnd.pd @@ -0,0 +1,68 @@ +#N canvas 33 53 661 597 10; +#X obj 133 20 inlet; +#X obj 189 171 vbap 0 0; +#X obj 240 20 inlet; +#X obj 359 20 inlet; +#X text 176 19 azimuth; +#X text 285 19 elevation; +#X text 405 19 spread; +#X obj 233 460 pack f f f; +#X obj 298 437 float \$1; +#X obj 298 414 loadbang; +#X msg 233 495 element \$3 \$1 \$2; +#X text 287 536 to matrix object; +#X obj 441 535 outlet; +#X obj 488 535 outlet; +#X obj 535 535 outlet; +#X obj 359 78 t b f; +#X obj 240 80 t b f; +#X obj 133 80 t b f; +#X obj 189 106 receive speaker_setup; +#X obj 189 256 unpack f f; +#X obj 189 279 + 1; +#X obj 167 429 ==; +#X obj 167 453 sel 1; +#X obj 189 303 t f f f; +#X obj 195 429 high; +#X obj 233 536 outlet; +#X obj 31 20 inlet~; +#X obj 31 83 outlet~; +#X text 28 113 for convenience; +#X floatatom 489 62 5 0 0; +#X obj 359 49 recent 10; +#X obj 240 51 recent 10; +#X obj 133 51 recent 10; +#X connect 0 0 32 0; +#X connect 1 0 19 0; +#X connect 1 1 12 0; +#X connect 1 2 13 0; +#X connect 1 3 14 0; +#X connect 2 0 31 0; +#X connect 3 0 30 0; +#X connect 7 0 10 0; +#X connect 8 0 7 2; +#X connect 9 0 8 0; +#X connect 10 0 25 0; +#X connect 15 0 1 0; +#X connect 15 1 1 3; +#X connect 16 0 1 0; +#X connect 16 1 1 2; +#X connect 17 0 1 0; +#X connect 17 1 1 1; +#X connect 18 0 1 0; +#X connect 19 0 20 0; +#X connect 19 1 7 1; +#X connect 20 0 23 0; +#X connect 21 0 22 0; +#X connect 22 0 25 0; +#X connect 23 0 21 0; +#X connect 23 1 24 0; +#X connect 23 2 7 0; +#X connect 24 0 21 1; +#X connect 26 0 27 0; +#X connect 29 0 30 1; +#X connect 29 0 31 1; +#X connect 29 0 32 1; +#X connect 30 0 15 0; +#X connect 31 0 16 0; +#X connect 32 0 17 0; -- cgit v1.2.1