/* * Copyright (c) 1983-2013 Richard Dobson and Composers Desktop Project Ltd * http://people.bath.ac.uk/masrwd * http://www.composersdesktop.com * This file is part of the CDP System. * The CDP System is free software; you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * The CDP System 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 Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with the CDP System; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /*********************************************************************** Motorola Motorola does not assume any liability ... Author: Tom Zudock (tomz@dsp.sps.mot.com) see: http://collaboration.cmc.ec.gc.ca/science/rpn/biblio/ddj/Website/articles/DDJ/1996/9612/9612d/9612d.htm Presented in Dr Dobbs Journal Vol21:12, December 1996. Filename: roomresp.cpp This program calculates the room response for a simple room. The output is not particularly delightful to listen to (it could sound phasey and have ringing due to the simple model used), but this program is the foundation for more complex virtual audio models which will eliminate these anomalies. Example form of the InputParametersTextFile: 0.5 # Input gain for data file (float) 1 # Open Path = 0 Closed Path = 1 (int) 6 5 4 # Room Dimensions (X,Y,Z) (float) 0.85 # Reflectivity of walls (float) 5 # Number of rooms per dimension, 1 or greater 2 3 2 # Listener coord (X,Y,Z) (float) 2 2 2 1 # Source coord and time at coord (X,Y,Z,t) (float) 3 2 2 1 # Source coord and time at coord (X,Y,Z,t) (float) 4 3 2 1 # Source coord and time at coord (X,Y,Z,t) (float) 3 4 2 1 # Source coord and time at coord (X,Y,Z,t) (float) 2 4 2 1 # Source coord and time at coord (X,Y,Z,t) (float) ***********************************************************************/ // included header files #include #include #include //#include #include #ifdef _DEBUG #include #endif #include #include using namespace std; #ifndef _MAX #define _MAX(x,y) ((x) > (y) ? (x) : (y)) #endif // macro defined constants //#define Vs 334.0 static const double Vs = 334.0; static const double srate = 20000; #define PI 3.14159265359 #define RefDist 1.0 enum args { REFLECTIVITY, REFLECTIONS, R_X, R_Y, R_Z, S_X, S_Y, S_Z, L_X, L_Y, L_Z }; // structure for a point in 3 dimensional space struct point { double X; double Y; double Z; point(){ X = Y = Z = 0.0; } virtual ~point() {} }; // structure for a point paired with a time duration struct position { point Coord; double Time; position *NextSource; }; const char* cdp_version = "5.0.0"; bool input_cmds(char *args[],int argc,point &room,point &source,point &listener,double &reflcoef,int &NR); void CalcImpResp( long *, double *, point &, point &, point &, double, double, int); position * InputParameters( FILE *, point &, // point &, point &, double &, double &, int &); void InputParamsError( int); //RWD stuff typedef struct deltap { long time; double val; } DELTAP; bool operator<(const DELTAP &a,const DELTAP &b) { return (a.time < b.time); } void usage() //RWD.1..12.98 left out flag -nNORM for now... { printf("\n********* ROOMRESP.EXE: by Tom Zudock CDP build 1998,1999 ******" "\n GENERATE ROOM-RESPONSE EARLY REFLECTIONS DATAFILE\n" "\nusage: roomresp [-aMAXAMP][-rRES] txtout.dat liveness nrefs " "\n roomL roomW roomH" "\n sourceL sourceW sourceH" "\n listenerL listenerW listenerH"); printf("\ntxtout.dat: text outfile containing early reflections in brkpoint format" "\n suitable for input to rmverb, reverb or tdelay" "\nMAXAMP : peak amp for data (0.0 < MAXAMP <= 1.0; default 1.0)" "\nRES : time resolution for reflections in msecs" "\n (0.1 <= RES <= 2; default 0.1)" "\nliveness : degree of reflection from each surface (0 to 1; typ: 0.95)" "\nnrefs : number of reflections from each surface(nrefs > 0; typ: 2 to 5)" "\n (WARNING: high values will create extremely long data files!)" "\nROOM PARAMETERS:" "\nroomL roomW roomH : room size: (Length, Width, Height)" "\nsourceL sourceW ourceH : position of sound source (as above)" "\nlistenerL listenerW listenerH: position of listener (as above)" "\n all dimensions are in metres" "\nNB: first output time is non-zero." ); } /*********************************************************************** main The main program is responsible for the primary program flow. It contains the primary processing loop which calls subroutines to handle the specialized functionality. ***********************************************************************/ int main (int argc, char **argv) { // FILE *PositionFile = 0; // input text parameters file //RWD to print earlies as text FILE *earliesFile = 0; #ifdef TESTING FILE *rawfile = 0; ///just for testing...raw output from impresp funtionm #endif int i,NR = 0; // num of mirrored rooms per dimension long *TapDelay = 0; // pointer to array of tap delays double Fs = 0.0; // the sampling frequency double *TapGain = 0; // pointer to array of tap gains double ReflCoef = 0.0; // the reflectivity of the walls // position *CurrentSource = 0; // current source in positions list point Room; // coords for the room size point Source; // coords of current sound source point Listener; // coords of the listener #ifdef _DEBUG bool write_c_struct = false; #endif bool do_normalize = true; double normfac = 1.0; double min_separation = 0.0001; if((argc==2) && strcmp(argv[1],"--version")==0) { fprintf(stdout,"%s\n",cdp_version); fflush(stdout); return 0; } if(argc==1){ usage(); exit(0); } if (argc < 13){ fprintf(stderr,"\ninsufficient arguments\n"); usage(); exit(-1); } while(argc > 0 && argv[1][0]=='-'){ double val; switch(argv[1][1]){ #ifdef _DEBUG //my private facility... case('c'): write_c_struct = true; printf("\nwriting C-format file"); break; #endif case('a'): if(argv[1][2]=='\0'){ fprintf(stderr,"\n-a flag requires a value\n"); usage(); exit(1); } val = atof(&(argv[1][2])); #ifdef _DEBUG if(val <0.0 || val > 1.0){ #else if(val <=0.0 || val > 1.0){ #endif fprintf(stderr,"\n normalization value %lf out of range\n",val); usage(); exit(1); } #ifdef _DEBUG if(val==0.0) { do_normalize = false; normfac = 1.0; printf("\nskipping normalization"); } else #endif normfac = val; break; case('r'): if(argv[1][2]=='\0'){ fprintf(stderr,"\n-r flag requires a value\n"); usage(); exit(1); } val = atof(&(argv[1][2])); if(val < 0.1 || val > 2.0) { fprintf(stderr,"\nvalue %.4lf for RES out of range\n",val); usage(); exit(1); } else min_separation = val * 0.001; break; default: fprintf(stderr,"\nbad flag option\n"); exit(1); break; } argc--; argv++; } //step along arglist: argc--; argv++; if(argc != 12){ fprintf(stderr,"\nincorrect number of numeric arguments\n"); usage(); exit(1); } if((earliesFile = fopen(argv[0],"w"))==NULL){ printf ("\nUnable to open %s for output\n",argv[2]); exit(-1); } #ifdef TESTING if((rawfile = fopen("rawdata.txt","w"))==NULL){ printf ("Unable to open rawdata.txt for output\n"); exit(-1); } #endif //step along again... argc--; argv++; //NB this sets the efective time-resolution for each tap, ie 1.0 / fs //so 1000 is a minimum, for 1msec resolution Fs = srate; if(!input_cmds(argv,argc,Room,Source,Listener,ReflCoef,NR)) exit(1); //already got the errmsg if((TapDelay = new long[NR*NR*NR])==NULL) { printf("Error: Unable to allocate memory\n"); exit(-1); } if((TapGain = new double[NR*NR*NR])==NULL) { printf("Error: Unable to allocate memory\n"); exit(-1); } CalcImpResp( TapDelay, TapGain, Room, /*CurrentSource->Coord,*/Source, Listener, ReflCoef, Fs, NR); //sort deltaps, eliminate duplicates, write to file vector vtaps; DELTAP thistap,prevtap; prevtap.time = 0; prevtap.val = 0.0; for(i = 0; i < NR*NR*NR;i++) { thistap.time = TapDelay[i]; thistap.val =TapGain[i]; #ifdef TESTING fprintf(rawfile,"%ld\t%.6lf\n",thistap.time,thistap.val); #endif //exclude tapval of 1.0: = direct sig at listening point if(thistap.val > 0.0 && thistap.val < 1.0) // get a problem in release build otherwise.... vtaps.push_back(thistap); } //now we can sort them sort(vtaps.begin(),vtaps.end()); vector::iterator it; it = vtaps.begin(); prevtap.time = it->time; prevtap.val = it->val; //print all taps as we got them, first! double maxval = 0.0; for(;it != vtaps.end();it++){ #ifdef NOTDEF fprintf(earliesFile,"\n\t%.4lf\t%.6lf",(double)it->time / Fs,it->val); #endif maxval = _MAX(it->val,maxval); } #ifdef _DEBUG printf("\noverall maxamp = %.4lf",maxval); #endif //print first line, formatted for code //normalize all vals to max amplitude! if(do_normalize){ normfac /= prevtap.val; #ifdef _DEBUG printf("\nnormalizing by %.4lf",normfac); #endif } it = vtaps.begin(); //normfac= floor(normfac); #ifdef _DEBUG printf("\ninitial time= %ld, initial amp = %.4lf",it->time,it->val); //RWD private facility! if(write_c_struct){ fprintf(earliesFile,"\n\n{\n\t{%.4lf,%.6lf}",(double)prevtap.time / Fs,prevtap.val * normfac); int count = 1; for(;it != vtaps.end();it++){ //get maxamp of any taps sharing a time (to 5 sig figs) - or should I add them somehow? if(fabs(it->time - prevtap.time) < min_separation){ it->val = _MAX(it->val,prevtap.val); continue; } fprintf(earliesFile,",\n\t{%.5lf,%.6lf}",(double)it->time / Fs,it->val * normfac); prevtap = *it; count++; } fprintf(earliesFile,"\n\t};\n\nNUMTAPS = %d\n",count); } else { #endif int count = 1; fprintf(earliesFile,"%.5lf\t%.6lf\n",(double)prevtap.time / Fs, prevtap.val * normfac); for(;it != vtaps.end();it++){ if(fabs(it->time - prevtap.time) < min_separation){ it->val = _MAX(it->val,prevtap.val); continue; } fprintf(earliesFile,"%.5lf\t%.6lf\n",(double)it->time / Fs, it->val * normfac); prevtap = *it; count++; } printf("\n\nNUMTAPS = %d\n",count); #ifdef _DEBUG } #endif fclose(earliesFile); #ifdef TESTING fclose(rawfile); #endif delete TapDelay, delete TapGain, printf ("done\n"); return(0); } /*********************************************************************** CalcImpResp This subroutine calculates the time delays (in samples) and attenuations for the early reflections in the room. ***********************************************************************/ void CalcImpResp( long * TapDelay, double * TapGain, point & Room, point & Source, point & Listener, double ReflCoef, double Fs, int NR) { double Dist = 0.0; // distance travelled by sound ray double ReflOrd = 0.0; // reflection order of sound ray //RWD.11.98 need volatile, or VC++ optimizer does more bad things! volatile point MirrSource; // mirrored source x,y,z coords //RWD int NRovr2 = NR / 2; int index = 0; for (int i=-NRovr2;i<=NRovr2;i++) { // loop through all 3 dimensions for (int j=-NRovr2;j<=NRovr2;j++) { for (int k=-NRovr2;k<=NRovr2;k++) { // calc x,y,z sound source coords in mirrored room MirrSource.X = (i)*Room.X +fabs(((i)%2)*Room.X) +pow(-1,(i))*Source.X; MirrSource.Y = (j)*Room.Y +fabs(((j)%2)*Room.Y) +pow(-1,(j))*Source.Y; MirrSource.Z = (k)*Room.Z +fabs(((k)%2)*Room.Z) +pow(-1,(k))*Source.Z; // calculate distance to listener Dist = sqrt(pow(MirrSource.X-Listener.X,2) +pow(MirrSource.Y-Listener.Y,2) +pow(MirrSource.Z-Listener.Z,2)); // calculate delayed arrival time of reflection //RWD problem with release build... index = (i+NRovr2)*NR*NR+(j+NRovr2)*NR+(k+NRovr2); //this also stopped the optimizer doing bad things.... //if(index%NR==0) // printf("\nindex = %d\tX= %.4lf\tY=%.4lf\tZ=%.4lf\tDist = %.4lf", // index,MirrSource.X,MirrSource.Y,MirrSource.Z,Dist); TapDelay[index]=Dist/Vs*Fs; ReflOrd = abs(i)+abs(j)+abs(k); // calculate attenuation for the reflection //RWD div/zero avoidance: if(Dist==0.0) TapGain[index] = 1.0; else TapGain[index]= pow(RefDist/Dist,2.0)*pow(ReflCoef,ReflOrd); } } } } /*********************************************************************** InputParameters This subroutine inputs parameters from a text file. An example of the format of the parameters file is below. It also checks the range of some of the parameters and creates a linked list representing how the sound source changes position in the modeled room. 0.5 # Input gain for data file (float) 1 # Open Path = 0 Closed Path = 1 (int) 6 5 4 # Room Dimensions (X,Y,Z) (float) 0.85 # Reflectivity of walls (float) 5 # Number of rooms per dimension, 1 or greater 2 3 2 # Listener coord (X,Y,Z) (float) 2 2 2 1 # Source coord and time at coord (X,Y,Z,t) (float) 3 2 2 1 # Source coord and time at coord (X,Y,Z,t) (float) 4 3 2 1 # Source coord and time at coord (X,Y,Z,t) (float) 3 4 2 1 # Source coord and time at coord (X,Y,Z,t) (float) 2 4 2 1 # Source coord and time at coord (X,Y,Z,t) (float) ***********************************************************************/ position* InputParameters( FILE *PositionFile, point &Room, // point &Source, point &Listener, double &ReflCoef, double &InGain, int &NR) { position *First = new position,*Previous=First,*Next=Previous; int ClosedPath,LineCount=1; char StrLine[256]; #define ReadParms(arg1) \ if ((fgets(StrLine,256,PositionFile)) != NULL) { \ if (arg1) \ LineCount++; \ else \ InputParamsError(LineCount); \ } \ else \ InputParamsError(0); #ifdef _DEBUG assert(PositionFile); #endif ReadParms(sscanf(StrLine,"%le",&InGain)==1) ReadParms(sscanf(StrLine,"%d",&ClosedPath)==1) ReadParms(sscanf(StrLine,"%le %le %le",&Room.X,&Room.Y,&Room.Z)==3) ReadParms(sscanf(StrLine,"%le",&ReflCoef)==1) ReadParms(sscanf(StrLine,"%d",&NR)==1) ReadParms(sscanf(StrLine,"%le %le %le",&Listener.X,&Listener.Y,&Listener.Z)==3) if (NR%2 != 1) { printf ("Error: The number of reflected rooms per dimension must be odd (for symmetry)\n"); exit(-1); } //RWD!!! was bitwise OR: '|' if ((Listener.Y > Room.Y)||(Listener.X > Room.X)) { // Check listener position printf ("Error: Listener position puts listener outside of room \n"); exit(-1); } // Choose a default position for listener //RWD check this! am I always getting these setings???? First->Coord.X = Room.X/2; First->Coord.Y = Room.Y/2; First->Coord.Z = Room.Z/2; First->Time = 0; First->NextSource=NULL; while (feof(PositionFile)==0) { if ((fgets(StrLine,256,PositionFile)) != NULL) { if(sscanf(StrLine,"%le %le %le %le",&(Next->Coord.X),&(Next->Coord.Y),&(Next->Coord.Z),&(Next->Time))==4) { Next->NextSource = new position; Previous = Next; Next = Next->NextSource; LineCount++; } else { InputParamsError(LineCount); } } } delete (Previous->NextSource); if (ClosedPath) Previous->NextSource=First; else Previous->NextSource=NULL; return(First); } //RWD get params from cmdline: refl,nrefs,rX,rY,rZ,sX,sY,sZ,lX,lY,lZ //fix ingain at 1.0 bool input_cmds(char *args[],int argc,point &room,point &source,point &listener,double &reflcoef,int &NR) { double dval; int ival; #ifdef _DEBUG assert(argc==11); #endif if(argc != 11) return false; dval = atof(args[REFLECTIVITY]); if(dval <= 0.0 || dval > 1.0){ fprintf(stderr,"\nbad value for liveliness\n"); return false; } reflcoef = dval; ival = atoi(args[REFLECTIONS]); if(ival < 1){ fprintf(stderr,"\nbad value for nrefs\n"); return false; } NR = 1 + (2* ival); room.X = atof(args[R_X]); if(room.X <= 0.0){ fprintf(stderr,"\nbad size for roomX\n"); return false; } room.Y = atof(args[R_Y]); if(room.Y <= 0.0){ fprintf(stderr,"\nbad size for roomY\n"); return false; } room.Z = atof(args[R_Z]); if(room.Z <= 0.0){ fprintf(stderr,"\nbad size for roomZ\n"); return false; } listener.X = atof(args[L_X]); if(listener.X < 0.0){ fprintf(stderr,"\nbad size for listenerX\n"); return false; } listener.Y = atof(args[L_Y]); if(listener.Y < 0.0){ fprintf(stderr,"\nbad size for listenerY\n"); return false; } listener.Z = atof(args[L_Z]); if(listener.Z < 0.0){ fprintf(stderr,"\nbad size for listenerZ\n"); return false; } if ((listener.Y > room.Y)||(listener.X > room.X)|| (listener.Z > room.Z)) { // Check listener position printf ("\nError: Listener position puts listener outside room\n"); return false; } source.X = atof(args[S_X]); if(source.X <= 0.0){ fprintf(stderr,"\nbad size for sourceX\n"); return false; } source.Y = atof(args[S_Y]); if(source.X <= 0.0){ fprintf(stderr,"\nbad size for sourceY\n"); return false; } source.Z = atof(args[S_Z]); if(source.X <= 0.0){ fprintf(stderr,"\nbad size for sourceX\n"); return false; } if ((source.Y > room.Y)||(source.X > room.X)|| (source.Z > room.Z)) { // Check source position printf ("\nError: Source position puts source outside room\n"); return false; } return true; } /*********************************************************************** InputParamsError This subroutine outputs an text error message to standard output when an error is encountered in the InputParams subroutine. The error text that is output is selected by the line number of the input parameters file where the error occured. ***********************************************************************/ void InputParamsError( int ErrNum) { char ErrorStr[][256] = { "Error: EOF reached before all parameters input\n", "Error: Line %d requires 1 parameter the gain for the input file\n", "Error: Line %d requires 1 parameter (0 or 1): 0=closed path, 1=open path\n", "Error: Line %d requires 3 parameters (X,Y,Z): the room dimensions\n", "Error: Line %d requires 1 parameter the wall refelctivity\n", "Error: Line %d requires 1 parameter the number of mirrored rooms per dimension\n", "Error: Line %d requires 3 parameters (X,Y,Z): the listeners position\n", "Error: Line %d requires 4 parameters (X,Y,Z,t): sound position position and time\n"}; if (ErrNum < 7) printf(ErrorStr[ErrNum],ErrNum); else printf(ErrorStr[7],ErrNum); exit(-1); }