/* * * gtk2 spectrum analyzer * * Copyright (C) 2004 Monty * * This analyzer is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * The analyzer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Postfish; see the file COPYING. If not, write to the * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * * */ #include "analyzer.h" static int blockslice[MAX_FILES]= {-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1}; static float **blockbuffer=0; static int blockbufferfill[MAX_FILES]={0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0}; static float *window; static float *freqbuffer=0; static fftwf_plan plan; static unsigned char readbuffer[MAX_FILES][readbuffersize]; static int readbufferfill[MAX_FILES]={0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0}; static int readbufferptr[MAX_FILES]={0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0}; static FILE *f[MAX_FILES]={0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0}; static off_t offset[MAX_FILES]={0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0}; static off_t length[MAX_FILES]= {-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1}; static off_t bytesleft[MAX_FILES]= {-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1}; int seekable[MAX_FILES]={0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0}; int global_seekable=0; pthread_mutex_t feedback_mutex=PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; int feedback_increment=0; float *feedback_count; float **plot_data; float *plot_floor=NULL; float **work_floor=NULL; float *process_work; float **feedback_acc; float **feedback_max; float **feedback_instant; /* Gentlemen, power up the Variance hammer */ float **floor_y; float **floor_yy; int floor_count; float **ph_acc; float **ph_max; float **ph_instant; float **xmappingL; float **xmappingH; int metascale = -1; int metawidth = -1; int metares = -1; int metanoise = 0; sig_atomic_t acc_clear=0; sig_atomic_t acc_rewind=0; sig_atomic_t acc_loop=0; sig_atomic_t process_active=0; sig_atomic_t process_exit=0; static int host_is_big_endian() { int32_t pattern = 0xfeedface; /* deadbeef */ unsigned char *bytewise = (unsigned char *)&pattern; if (bytewise[0] == 0xfe) return 1; return 0; } /* Macros to read header data */ #define READ_U32_LE(buf) \ (((buf)[3]<<24)|((buf)[2]<<16)|((buf)[1]<<8)|((buf)[0]&0xff)) #define READ_U16_LE(buf) \ (((buf)[1]<<8)|((buf)[0]&0xff)) #define READ_U32_BE(buf) \ (((buf)[0]<<24)|((buf)[1]<<16)|((buf)[2]<<8)|((buf)[3]&0xff)) #define READ_U16_BE(buf) \ (((buf)[0]<<8)|((buf)[1]&0xff)) double read_IEEE80(unsigned char *buf){ int s=buf[0]&0xff; int e=((buf[0]&0x7f)<<8)|(buf[1]&0xff); double f=((unsigned long)(buf[2]&0xff)<<24)| ((buf[3]&0xff)<<16)| ((buf[4]&0xff)<<8) | (buf[5]&0xff); if(e==32767){ if(buf[2]&0x80) return HUGE_VAL; /* Really NaN, but this won't happen in reality */ else{ if(s) return -HUGE_VAL; else return HUGE_VAL; } } f=ldexp(f,32); f+= ((buf[6]&0xff)<<24)| ((buf[7]&0xff)<<16)| ((buf[8]&0xff)<<8) | (buf[9]&0xff); return ldexp(f, e-16446); } static int find_chunk(FILE *in, char *type, unsigned int *len, int endian){ unsigned int i; unsigned char buf[8]; while(1){ if(fread(buf,1,8,in) <8)return 0; if(endian) *len = READ_U32_BE(buf+4); else *len = READ_U32_LE(buf+4); if(memcmp(buf,type,4)){ if((*len) & 0x1)(*len)++; for(i=0;i<*len;i++) if(fgetc(in)==EOF)return 0; }else return 1; } } int input_load(void){ int stdinp=0,i,fi; if(inputs==0){ /* look at stdin... is it a file, pipe, tty...? */ if(isatty(STDIN_FILENO)){ fprintf(stderr, "Spectrum requires either an input file on the command line\n" "or stream data piped|redirected to stdin. spectrum -h will\n" "give more details.\n"); return 1; } stdinp=1; /* file coming in via stdin */ inputname[0]=strdup("stdin"); inputs++; } for(fi=0;fi8)signedp[fi]=1; } if(bigendian[fi]==-1)bigendian[fi]=0; if(rate[fi]==-1){ if(lrate<4000 || lrate>192000){ fprintf(stderr,"%s:\n\tSampling rate out of bounds\n",inputname[fi]); return 1; } rate[fi]=lrate; } if(find_chunk(f[fi],"data",&chunklen,0)){ off_t pos=ftello(f[fi]); int bytes=(bits[fi]+7)/8; if(seekable[fi]) filelength= (filelength-pos)/ (channels[fi]*bytes)* (channels[fi]*bytes)+pos; if(chunklen==0UL || chunklen==0x7fffffffUL || chunklen==0xffffffffUL){ if(filelength==-1){ length[fi]=-1; fprintf(stderr,"%s: Incomplete header; assuming stream.\n",inputname[fi]); }else{ length[fi]=(filelength-pos)/(channels[fi]*bytes); fprintf(stderr,"%s: Incomplete header; using actual file size.\n",inputname[fi]); } }else if(filelength==-1 || chunklen+pos<=filelength){ length[fi]=(chunklen/(channels[fi]*bytes)); fprintf(stderr,"%s: Using declared file size (%ld).\n", inputname[fi],(long)length[fi]*channels[fi]*bytes); }else{ length[fi]=(filelength-pos)/(channels[fi]*bytes); fprintf(stderr,"%s: File truncated; Using actual file size.\n",inputname[fi]); } offset[fi]=ftello(f[fi]); } else { fprintf(stderr,"%s: WAVE file has no \"data\" chunk following \"fmt \".\n",inputname[fi]); return 1; } }else{ fprintf(stderr,"%s: WAVE file has no \"fmt \" chunk.\n",inputname[fi]); return 1; } }else if(!strncmp(headerid,"FORM",4) && !strncmp(headerid+8,"AIF",3)){ unsigned int len; int aifc=0; if(headerid[11]=='C')aifc=1; unsigned char *buffer; char buf2[8]; int lch; int lbits; int lrate; int bytes; /* look for COMM */ if(!find_chunk(f[fi], "COMM", &len,1)){ fprintf(stderr,"%s: AIFF file has no \"COMM\" chunk.\n",inputname[fi]); return 1; } if(len < 18 || (aifc && len<22)) { fprintf(stderr,"%s: AIFF COMM chunk is truncated.\n",inputname[fi]); return 1; } buffer = alloca(len); if(fread(buffer,1,len,f[fi]) < len){ fprintf(stderr, "%s: Unexpected EOF in reading AIFF header\n",inputname[fi]); return 1; } lch = READ_U16_BE(buffer); lbits = READ_U16_BE(buffer+6); lrate = (int)read_IEEE80(buffer+8); if(bits[fi]==-1)bits[fi]=lbits; bytes=(bits[fi]+7)/8; if(signedp[fi]==-1)signedp[fi]=1; if(rate[fi]==-1){ if(lrate<4000 || lrate>192000){ fprintf(stderr,"%s:\n\tSampling rate out of bounds\n",inputname[fi]); return 1; } rate[fi]=lrate; } if(channels[fi]==-1)channels[fi]=lch; if(bigendian[fi]==-1){ if(aifc){ if(!memcmp(buffer+18, "NONE", 4)) { bigendian[fi] = 1; }else if(!memcmp(buffer+18, "sowt", 4)) { bigendian[fi] = 0; }else{ fprintf(stderr, "%s: Spectrum supports only linear PCM AIFF-C files.\n",inputname[fi]); return 1; } }else bigendian[fi] = 1; } if(!find_chunk(f[fi], "SSND", &len, 1)){ fprintf(stderr,"%s: AIFF file has no \"SSND\" chunk.\n",inputname[fi]); return 1; } if(fread(buf2,1,8,f[fi]) < 8){ fprintf(stderr,"%s: Unexpected EOF reading AIFF header\n",inputname[fi]); return 1; } { int loffset = READ_U32_BE(buf2); int lblocksize = READ_U32_BE(buf2+4); /* swallow some data */ for(i=0;iblocksize/2)blockslice[fi]/=2; total_ch += channels[fi]; if(length[fi]!=-1) bytesleft[fi]=length[fi]*channels[fi]*((bits[fi]+7)/8); }else{ fprintf(stderr,"Unable to open %s: %s\n",inputname[fi],strerror(errno)); exit(1); } } blockbuffer=malloc(total_ch*sizeof(*blockbuffer)); process_work=calloc(blocksize+2,sizeof(*process_work)); feedback_count=calloc(total_ch,sizeof(*feedback_count)); plot_data=calloc(total_ch,sizeof(*plot_data)); feedback_acc=malloc(total_ch*sizeof(*feedback_acc)); feedback_max=malloc(total_ch*sizeof(*feedback_max)); feedback_instant=malloc(total_ch*sizeof(*feedback_instant)); floor_y=malloc(total_ch*sizeof(*floor_y)); floor_yy=malloc(total_ch*sizeof(*floor_yy)); ph_acc=malloc(total_ch*sizeof(*ph_acc)); ph_max=malloc(total_ch*sizeof(*ph_max)); ph_instant=malloc(total_ch*sizeof(*ph_instant)); freqbuffer=fftwf_malloc((blocksize+2)*sizeof(*freqbuffer)); for(i=0;i0){ memmove(readbuffer[fi],readbuffer[fi]+readbufferptr[fi], (readbufferfill[fi]-readbufferptr[fi])*sizeof(**readbuffer)); readbufferfill[fi]-=readbufferptr[fi]; readbufferptr[fi]=0; }else{ readbufferfill[fi]=0; readbufferptr[fi]=0; } /* attempt to top off the readbuffer */ { long actually_readbytes=0,readbytes=readbuffersize-readbufferfill[fi]; if(readbytes>0) actually_readbytes=fread(readbuffer[fi]+readbufferfill[fi],1,readbytes,f[fi]); if(actually_readbytes<0){ fprintf(stderr,"Input read error from %s: %s\n",inputname[fi],strerror(errno)); }else if (actually_readbytes==0){ /* don't process any partially-filled blocks; the stairstep at the end could pollute results badly */ memset(readbuffer[fi],0,readbuffersize); bytesleft[fi]=0; readbufferfill[fi]=0; readbufferptr[fi]=0; blockbufferfill[fi]=0; }else{ bytesleft[fi]-=actually_readbytes; readbufferfill[fi]+=actually_readbytes; /* conditionally clear global EOF */ if(acc_loop){ if(seekable[fi])eof=0; }else{ eof=0; } notdone=1; } } } ch += channels[fi]; } } return eof; } void rundata_clear(){ int i,j; for(i=0;i>1]+=sqM*sqM; floor_y[i][j>>1]+=sqM; } /* deal with phase accumulate/rotate */ if(i==ch){ /* normalize/store ref for later rotation */ process_work[j] = R; process_work[j+1] = -I; }else{ /* rotate signed square phase according to ref for phase calculation */ float pR; float pI; float rR = process_work[j]; float rI = process_work[j+1]; pR = (rR*R - rI*I); pI = (rR*I + rI*R); ph_instant[i][j]=pR; ph_instant[i][j+1]=pI; ph_acc[i][j]+=pR; ph_acc[i][j+1]+=pI; if(feedback_max[i][j>>1]>1]=sqM; feedback_acc[i][j>>1]+=sqM; if(feedback_max[i][j>>1]>1]=sqM; } feedback_count[i]++; pthread_mutex_unlock(&feedback_mutex); } } ch+=channels[fi]; } if(noise==1) floor_count++; feedback_increment++; write(eventpipe[1],"",1); return 1; } void *process_thread(void *dummy){ while(!process_exit && process()); process_active=0; write(eventpipe[1],"",1); return NULL; } void process_dump(int mode){ int fi,i,j,ch; FILE *out; { out=fopen("accumulate.m","w"); ch = 0; for(fi=0;fiblocksize/2.)xmappingL[fi][i]=blocksize/2.; if(xmappingH[fi][i]<0.)xmappingH[fi][i]=0.; if(xmappingH[fi][i]>blocksize/2.)xmappingH[fi][i]=blocksize/2.; } } for(i=0;iy[i])y[i]=esum; } } ch+=channels[fi]; } } if(link == LINK_INDEPENDENT && mode==0) *yfloor=plot_floor; }else{ for(i=0;i*ymax)*ymax=sum; y[i]=sum; } } } break; case LINK_SUMMED: { float *y = plot_data[ch]; memset(y,0,(width+1)*sizeof(*y)); for(ci=0;ci*ymax)*ymax=sum; y[i]=sum; } } break; case LINK_SUB_FROM: { float *y = plot_data[ch]; if(active[ch]==0){ for(i=0;i0?y[i]:0); float sum=todB_a(&v)*.5; if(sum>*ymax)*ymax=sum; y[i]=sum; } } } break; case LINK_SUB_REF: { float *r = plot_data[ch]; for(ci=0;cisum?0.f:sum-r[i]); y[i]=todB_a(&sum)*.5; if(y[i]>*ymax)*ymax=y[i]; } } } } } break; case LINK_IMPEDENCE_p1: case LINK_IMPEDENCE_1: case LINK_IMPEDENCE_10: { float shunt = (link == LINK_IMPEDENCE_p1?.1:(link == LINK_IMPEDENCE_1?1:10)); float *r = plot_data[ch]; for(ci=0;ci(1e-5) && V>S){ y[i] = shunt*(V-S)/S; }else{ y[i] = NAN; } } } } } /* scan the resulting buffers for marginal data that would produce spurious output. Specifically we look for sharp falloffs of > 40dB or an original test magnitude under -70dB. */ { float max = -140; for(i=0;imax)max=v; } for(ci=1;cimax-40 && r[j]>-70)break; y[j]=NAN; } i=j+3; for(;j*ymax)*ymax = y[i]; } } } } } break; case LINK_PHASE: /* response/phase */ if(channels[fi]>=2){ float *om = plot_data[ch]; float *op = plot_data[ch+1]; float *r = data[ch]; float *rn = work_floor[ch]; float *m = data[ch+1]; float *mn = work_floor[ch+1]; float *p = ph[ch+1]; float mag[width]; if(feedback_count[ch]==0){ memset(om,0,width*sizeof(*om)); memset(op,0,width*sizeof(*op)); }else{ /* two vectors only; response and phase */ /* response */ if(active[ch] || active[ch+1]){ for(i=0;irn[i] && sumM>mn[i]){ mag[i] = todB_a(&sumR)*.5; sumM /= sumR; om[i] = todB_a(&sumM)*.5; }else{ om[i] = NAN; } } } /* phase */ if(active[ch+1]){ for(i=0;i 40dB or an original test magnitude under -70dB. */ if(active[ch] || active[ch+1]){ for(i=0;i*ymax)*ymax = om[i]; if(op[i]>*pmax)*pmax = op[i]; if(op[i]<*pmin)*pmin = op[i]; } } } } break; case LINK_THD: /* THD */ case LINK_THD2: /* THD-2 */ case LINK_THDN: /* THD+N */ case LINK_THDN2: /* THD+N-2 */ break; } ch+=channels[fi]; } return plot_data; }