/* WDL - convoengine.cpp Copyright (C) 2006 and later Cockos Incorporated This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ #ifdef _WIN32 #include #endif #include #include #include #include #include "convoengine.h" #include "denormal.h" //#define TIMING #include "timing.c" #define CONVOENGINE_SILENCE_THRESH 1.0e-12 // -240dB #define CONVOENGINE_IMPULSE_SILENCE_THRESH 1.0e-15 // -300dB static void WDL_CONVO_CplxMul2(WDL_FFT_COMPLEX *c, WDL_FFT_COMPLEX *a, WDL_CONVO_IMPULSEBUFCPLXf *b, int n) { WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; if (n<2 || (n&1)) return; do { t1 = a[0].re * b[0].re; t2 = a[0].im * b[0].im; t3 = a[0].im * b[0].re; t4 = a[0].re * b[0].im; t5 = a[1].re * b[1].re; t6 = a[1].im * b[1].im; t7 = a[1].im * b[1].re; t8 = a[1].re * b[1].im; t1 -= t2; t3 += t4; t5 -= t6; t7 += t8; c[0].re = t1; c[1].re = t5; c[0].im = t3; c[1].im = t7; a += 2; b += 2; c += 2; } while (n -= 2); } static void WDL_CONVO_CplxMul3(WDL_FFT_COMPLEX *c, WDL_FFT_COMPLEX *a, WDL_CONVO_IMPULSEBUFCPLXf *b, int n) { WDL_FFT_REAL t1, t2, t3, t4, t5, t6, t7, t8; if (n<2 || (n&1)) return; do { t1 = a[0].re * b[0].re; t2 = a[0].im * b[0].im; t3 = a[0].im * b[0].re; t4 = a[0].re * b[0].im; t5 = a[1].re * b[1].re; t6 = a[1].im * b[1].im; t7 = a[1].im * b[1].re; t8 = a[1].re * b[1].im; t1 -= t2; t3 += t4; t5 -= t6; t7 += t8; c[0].re += t1; c[1].re += t5; c[0].im += t3; c[1].im += t7; a += 2; b += 2; c += 2; } while (n -= 2); } static bool CompareQueueToBuf(WDL_FastQueue *q, const void *data, int len) { int offs=0; while (len>0) { void *td=NULL; int sz=q->GetPtr(offs,&td); if (sz<1) return true; // not enough data = not equal! if (sz>len) sz=len; int i=sz/sizeof(WDL_FFT_REAL); WDL_FFT_REAL *a1=(WDL_FFT_REAL*)td; WDL_FFT_REAL *b1=(WDL_FFT_REAL*)data; while (i--) { if (fabs(*a1-*b1)>CONVOENGINE_SILENCE_THRESH) return true; a1++; b1++; } data = ((char *)data)+sz; offs+=sz; len-=sz; } return false; } WDL_ConvolutionEngine::WDL_ConvolutionEngine() { WDL_fft_init(); m_fft_size=0; m_impdata.Add(new ImpChannelInfo); m_impulse_len=0; m_proc_nch=0; } WDL_ConvolutionEngine::~WDL_ConvolutionEngine() { m_impdata.Empty(true); m_proc.Empty(true); } int WDL_ConvolutionEngine::SetImpulse(WDL_ImpulseBuffer *impulse, int fft_size, int impulse_sample_offset, int max_imp_size, bool forceBrute) { int impulse_len=0; int x; int nch=impulse->GetNumChannels(); for (x = 0; x < nch; x ++) { int l=impulse->impulses[x].GetSize()-impulse_sample_offset; if (max_imp_size && l>max_imp_size) l=max_imp_size; if (impulse_len < l) impulse_len=l; } if (nch>1) // detect mono signals pretending to be multichannel { for (x = 1; x < nch; x ++) { if (impulse->impulses[x].GetSize()!=impulse->impulses[0].GetSize()|| memcmp(impulse->impulses[x].Get(),impulse->impulses[0].Get(), impulse->impulses[0].GetSize()*sizeof(WDL_FFT_REAL))) break; } if (x >= nch) nch=1; } m_impulse_len=impulse_len; m_proc_nch=-1; while (m_impdata.GetSize() > nch) m_impdata.Delete(m_impdata.GetSize()-1,true); while (m_impdata.GetSize() < nch) m_impdata.Add(new ImpChannelInfo); if (forceBrute) { m_fft_size=0; // save impulse for (x = 0; x < m_impdata.GetSize(); x ++) { WDL_FFT_REAL *imp=impulse->impulses[x].Get()+impulse_sample_offset; int lenout=impulse->impulses[x].GetSize()-impulse_sample_offset; if (max_imp_size && lenout>max_imp_size) lenout=max_imp_size; WDL_CONVO_IMPULSEBUFf *impout=m_impdata.Get(x)->imp.Resize(lenout)+lenout; while (lenout-->0) *--impout = (WDL_CONVO_IMPULSEBUFf) *imp++; } for (x = 0; x < m_proc.GetSize(); x ++) { ProcChannelInfo *inf = m_proc.Get(x); inf->samplesin.Clear(); inf->samplesin2.Clear(); inf->samplesout.Clear(); } return 0; } if (fft_size<=0) { int msz=fft_size<=-16? -fft_size*2 : 32768; fft_size=32; while (fft_size < impulse_len*2 && fft_size < msz) fft_size*=2; } m_fft_size=fft_size; int impchunksize=fft_size/2; int nblocks=(impulse_len+impchunksize-1)/impchunksize; //wdl_log("il=%d, ffts=%d, cs=%d, nb=%d\n",impulse_len,fft_size,impchunksize,nblocks); const bool smallerSizeMode=sizeof(WDL_CONVO_IMPULSEBUFf)!=sizeof(WDL_FFT_REAL); WDL_FFT_REAL scale=(WDL_FFT_REAL) (1.0/fft_size); for (x = 0; x < m_impdata.GetSize(); x ++) { WDL_FFT_REAL *imp=impulse->impulses[x].Get()+impulse_sample_offset; WDL_FFT_REAL *imp2=x < m_impdata.GetSize()-1 ? impulse->impulses[x+1].Get()+impulse_sample_offset : NULL; WDL_CONVO_IMPULSEBUFf *impout=m_impdata.Get(x)->imp.Resize((nblocks+!!smallerSizeMode)*fft_size*2); char *zbuf=m_impdata.Get(x)->zflag.Resize(nblocks); int lenout=impulse->impulses[x].GetSize()-impulse_sample_offset; if (max_imp_size && lenout>max_imp_size) lenout=max_imp_size; int bl; for (bl = 0; bl < nblocks; bl ++) { int thissz=lenout; if (thissz > impchunksize) thissz=impchunksize; lenout -= thissz; int i=0; WDL_FFT_REAL mv=0.0; WDL_FFT_REAL mv2=0.0; WDL_FFT_REAL *imptmp = (WDL_FFT_REAL *)impout; //-V615 for (; i < thissz; i ++) { WDL_FFT_REAL v=*imp++; WDL_FFT_REAL v2=(WDL_FFT_REAL)fabs(v); if (v2 > mv) mv=v2; imptmp[i*2]=denormal_filter_aggressive(v * scale); if (imp2) { v=*imp2++; v2=(WDL_FFT_REAL)fabs(v); if (v2>mv2) mv2=v2; imptmp[i*2+1]=denormal_filter_aggressive(v*scale); } else imptmp[i*2+1]=0.0; } for (; i < fft_size; i ++) { imptmp[i*2]=0.0; imptmp[i*2+1]=0.0; } if (mv>CONVOENGINE_IMPULSE_SILENCE_THRESH||mv2>CONVOENGINE_IMPULSE_SILENCE_THRESH) { *zbuf++=mv>CONVOENGINE_IMPULSE_SILENCE_THRESH ? 2 : 1; // 1 means only second channel has content WDL_fft((WDL_FFT_COMPLEX*)impout,fft_size,0); if (smallerSizeMode) { int x,n=fft_size*2; for(x=0;xsamplesin.Clear(); inf->samplesin2.Clear(); inf->samplesout.Clear(); inf->hist_pos = 0; memset(inf->samplehist_zflag.Get(),0,inf->samplehist_zflag.GetSize()); memset(inf->samplehist.Get(),0,inf->samplehist.GetSize()*sizeof(WDL_FFT_REAL)); memset(inf->overlaphist.Get(),0,inf->overlaphist.GetSize()*sizeof(WDL_FFT_REAL)); } } void WDL_ConvolutionEngine::Add(WDL_FFT_REAL **bufs, int len, int nch) { while (m_proc.GetSize() < nch) m_proc.Add(new ProcChannelInfo); while (m_proc.GetSize() > nch) m_proc.Delete(m_proc.GetSize()-1,true); if (m_fft_size<1) { m_proc_nch=nch; for (int ch = 0; ch < nch; ch ++) { int wch = ch % m_impdata.GetSize(); WDL_CONVO_IMPULSEBUFf *imp=m_impdata.Get(wch)->imp.Get(); int imp_len = m_impdata.Get(wch)->imp.GetSize(); ProcChannelInfo *pinf = m_proc.Get(ch); if (imp_len>0) { if (pinf->samplesin2.Available()samplesin2.Available(); memset(pinf->samplesin2.Add(NULL,sza),0,sza); } WDL_FFT_REAL *psrc; if (bufs && bufs[ch]) psrc=(WDL_FFT_REAL*)pinf->samplesin2.Add(bufs[ch],len*sizeof(WDL_FFT_REAL)); else { psrc=(WDL_FFT_REAL*)pinf->samplesin2.Add(NULL,len*sizeof(WDL_FFT_REAL)); memset(psrc,0,len*sizeof(WDL_FFT_REAL)); } WDL_FFT_REAL *pout=(WDL_FFT_REAL*)pinf->samplesout.Add(NULL,len*sizeof(WDL_FFT_REAL)); int x; int len1 = len&~1; for (x=0; x < len1 ; x += 2) { int i=imp_len; double sum=0.0,sum2=0.0; WDL_FFT_REAL *sp=psrc+x-imp_len + 1; WDL_CONVO_IMPULSEBUFf *ip=imp; int j=i/4; i&=3; while (j--) // produce 2 samples, 4 impulse samples at a time { double a = ip[0],b=ip[1],aa=ip[2],bb=ip[3]; double c = sp[1],d=sp[2],cc=sp[3]; sum+=a * sp[0] + b * c + aa * d + bb * cc; sum2+=a * c + b * d + aa * cc + bb * sp[4]; ip+=4; sp+=4; } while (i--) { double a = *ip++; sum+=a * sp[0]; sum2+=a * sp[1]; sp++; } pout[x]=(WDL_FFT_REAL) sum; pout[x+1]=(WDL_FFT_REAL) sum2; } for(;xsamplesin2.Advance(len*sizeof(WDL_FFT_REAL)); pinf->samplesin2.Compact(); } else { if (bufs && bufs[ch]) pinf->samplesout.Add(bufs[ch],len*sizeof(WDL_FFT_REAL)); else { memset(pinf->samplesout.Add(NULL,len*sizeof(WDL_FFT_REAL)),0,len*sizeof(WDL_FFT_REAL)); } } } return; } int impchunksize=m_fft_size/2; int nblocks=(m_impulse_len+impchunksize-1)/impchunksize; if (m_proc_nch != nch) { m_proc_nch=nch; int mso=0; for (int ch = 0; ch < nch; ch ++) { ProcChannelInfo *pinf = m_proc.Get(ch); pinf->hist_pos = 0; int so=pinf->samplesin.Available() + pinf->samplesout.Available(); if (so>mso) mso=so; if (m_impulse_len<1||!nblocks) { if (pinf->samplesin.Available()) { int s=pinf->samplesin.Available(); void *buf=pinf->samplesout.Add(NULL,s); pinf->samplesin.GetToBuf(0,buf,s); pinf->samplesin.Clear(); } } if (so < mso) { memset(pinf->samplesout.Add(NULL,mso-so),0,mso-so); } const int sz=nblocks*m_fft_size; memset(pinf->samplehist_zflag.Resize(nblocks),0,nblocks); pinf->samplehist.Resize(sz*2); pinf->overlaphist.Resize(m_fft_size/2); memset(pinf->samplehist.Get(),0,pinf->samplehist.GetSize()*sizeof(WDL_FFT_REAL)); memset(pinf->overlaphist.Get(),0,pinf->overlaphist.GetSize()*sizeof(WDL_FFT_REAL)); } } if (m_impulse_len<1||!nblocks) { for (int ch = 0; ch < nch; ch ++) { ProcChannelInfo *pinf = m_proc.Get(ch); if (bufs && bufs[ch]) pinf->samplesout.Add(bufs[ch],len*sizeof(WDL_FFT_REAL)); else memset(pinf->samplesout.Add(NULL,len*sizeof(WDL_FFT_REAL)),0,len*sizeof(WDL_FFT_REAL)); } // pass through return; } for (int ch = 0; ch < nch; ch ++) { ProcChannelInfo *pinf = m_proc.Get(ch); if (!pinf->samplehist.GetSize()||!pinf->overlaphist.GetSize()) continue; pinf->samplesin.Add(bufs ? bufs[ch] : NULL,len*sizeof(WDL_FFT_REAL)); } } void WDL_ConvolutionEngine::AddSilenceToOutput(int len) { for (int ch = 0; ch < m_proc_nch; ch++) { ProcChannelInfo *pinf = m_proc.Get(ch); memset(pinf->samplesout.Add(NULL,len*sizeof(WDL_FFT_REAL)),0,len*sizeof(WDL_FFT_REAL)); } } int WDL_ConvolutionEngine::Avail(int want) { if (m_fft_size<1) { ProcChannelInfo *pinf = m_proc.Get(0); return pinf ? pinf->samplesout.Available()/sizeof(WDL_FFT_REAL) : 0; } const int sz=m_fft_size/2; const int chunksize=m_fft_size/2; const int nblocks=(m_impulse_len+chunksize-1)/chunksize; // clear combining buffer WDL_FFT_REAL *workbuf2 = m_combinebuf.Resize(m_fft_size*4); // temp space int ch; for (ch = 0; ch < m_proc_nch; ch ++) { ProcChannelInfo *pinf = m_proc.Get(ch); ProcChannelInfo *pinf2 = ch+1 < m_proc_nch ? m_proc.Get(ch+1) : NULL; if (!pinf->samplehist.GetSize()||!pinf->overlaphist.GetSize()) continue; int srcc=ch % m_impdata.GetSize(); bool allow_mono_input_mode=true; bool mono_impulse_mode=false; if (m_impdata.GetSize()==1 && pinf2 && pinf2->samplehist.GetSize()&&pinf2->overlaphist.GetSize() && pinf->samplesin.Available()==pinf2->samplesin.Available() && pinf->samplesout.Available()==pinf2->samplesout.Available() ) { // 2x processing mode mono_impulse_mode=true; allow_mono_input_mode=false; } const int in_needed=sz; // useSilentList[x] = 1 for mono signal, 2 for stereo, 0 for silent char *useSilentList=pinf->samplehist_zflag.GetSize()==nblocks ? pinf->samplehist_zflag.Get() : NULL; while (pinf->samplesin.Available()/(int)sizeof(WDL_FFT_REAL) >= sz && pinf->samplesout.Available() < want*(int)sizeof(WDL_FFT_REAL)) { int histpos; if ((histpos=++pinf->hist_pos) >= nblocks) histpos=pinf->hist_pos=0; // get samples from input, to history WDL_FFT_REAL *optr = pinf->samplehist.Get()+histpos*m_fft_size*2; pinf->samplesin.GetToBuf(0,optr+sz,in_needed*sizeof(WDL_FFT_REAL)); pinf->samplesin.Advance(in_needed*sizeof(WDL_FFT_REAL)); bool mono_input_mode=false; bool nonzflag=false; if (mono_impulse_mode) { if (++pinf2->hist_pos >= nblocks) pinf2->hist_pos=0; pinf2->samplesin.GetToBuf(0,workbuf2,sz*sizeof(WDL_FFT_REAL)); pinf2->samplesin.Advance(sz*sizeof(WDL_FFT_REAL)); int i; for (i = 0; i < sz; i ++) // unpack samples { WDL_FFT_REAL f = optr[i*2]=denormal_filter_aggressive(optr[sz+i]); if (!nonzflag && (f<-CONVOENGINE_SILENCE_THRESH || f>CONVOENGINE_SILENCE_THRESH)) nonzflag=true; f=optr[i*2+1]=denormal_filter_aggressive(workbuf2[i]); if (!nonzflag && (f<-CONVOENGINE_SILENCE_THRESH || f>CONVOENGINE_SILENCE_THRESH)) nonzflag=true; } } else { if (allow_mono_input_mode && pinf2 && srccsamplesin,optr+sz,sz*sizeof(WDL_FFT_REAL)) ) { mono_input_mode=true; } else { allow_mono_input_mode=false; } int i; for (i = 0; i < sz; i ++) // unpack samples { WDL_FFT_REAL f=optr[i*2]=denormal_filter_aggressive(optr[sz+i]); optr[i*2+1]=0.0; if (!nonzflag && (f<-CONVOENGINE_SILENCE_THRESH || f>CONVOENGINE_SILENCE_THRESH)) nonzflag=true; } } int i; for (i = 1; mono_input_mode && i < nblocks; i ++) // start @ 1, since hist[histpos] is no longer used for here { int srchistpos = histpos-i; if (srchistpos < 0) srchistpos += nblocks; if (!useSilentList || useSilentList[srchistpos]==2) mono_input_mode=false; } if (nonzflag||!useSilentList) memset(optr+sz*2,0,sz*2*sizeof(WDL_FFT_REAL)); #ifdef WDLCONVO_ZL_ACCOUNTING m_zl_fftcnt++; #endif if (nonzflag) WDL_fft((WDL_FFT_COMPLEX*)optr,m_fft_size,0); if (useSilentList) useSilentList[histpos]=nonzflag ? (mono_input_mode ? 1 : 2) : 0; int mzfl=2; if (mono_input_mode) { mzfl=1; pinf2->samplesin.Advance(sz*sizeof(WDL_FFT_REAL)); // save a valid copy in sample hist incase we switch from mono to stereo if (++pinf2->hist_pos >= nblocks) pinf2->hist_pos=0; if (pinf2->samplehist_zflag.GetSize()==nblocks) pinf2->samplehist_zflag.Get()[pinf2->hist_pos] = nonzflag ? 1 : 0; WDL_FFT_REAL *optr2 = pinf2->samplehist.Get()+pinf2->hist_pos*m_fft_size*2; memcpy(optr2,optr,m_fft_size*2*sizeof(WDL_FFT_REAL)); } int applycnt=0; char *useImpSilentList=m_impdata.Get(srcc)->zflag.GetSize() == nblocks ? m_impdata.Get(srcc)->zflag.Get() : NULL; WDL_CONVO_IMPULSEBUFf *impulseptr=m_impdata.Get(srcc)->imp.Get(); for (i = 0; i < nblocks; i ++, impulseptr+=m_fft_size*2) { int srchistpos = histpos-i; if (srchistpos < 0) srchistpos += nblocks; if (useImpSilentList && useImpSilentList[i]samplehist.Get() + m_fft_size*srchistpos*2; if (applycnt++) // add to output WDL_CONVO_CplxMul3((WDL_FFT_COMPLEX*)workbuf2,(WDL_FFT_COMPLEX*)samplehist,(WDL_CONVO_IMPULSEBUFCPLXf*)impulseptr,m_fft_size); else // replace output WDL_CONVO_CplxMul2((WDL_FFT_COMPLEX*)workbuf2,(WDL_FFT_COMPLEX*)samplehist,(WDL_CONVO_IMPULSEBUFCPLXf*)impulseptr,m_fft_size); } if (!applycnt) memset(workbuf2,0,m_fft_size*2*sizeof(WDL_FFT_REAL)); else WDL_fft((WDL_FFT_COMPLEX*)workbuf2,m_fft_size,1); WDL_FFT_REAL *olhist=pinf->overlaphist.Get(); // errors from last time WDL_FFT_REAL *p1=workbuf2,*p3=workbuf2+m_fft_size,*p1o=workbuf2; if (mono_impulse_mode||mono_input_mode) { WDL_FFT_REAL *p2o=workbuf2+m_fft_size*2; WDL_FFT_REAL *olhist2=pinf2->overlaphist.Get(); // errors from last time int s=sz/2; while (s--) { p2o[0] = p1[1]+olhist2[0]; p2o[1] = p1[3]+olhist2[1]; p1o[0] = p1[0]+olhist[0]; p1o[1] = p1[2]+olhist[1]; p1o+=2; p2o+=2; p1+=4; olhist[0]=p3[0]; olhist[1]=p3[2]; olhist2[0]=p3[1]; olhist2[1]=p3[3]; p3+=4; olhist+=2; olhist2+=2; } // add samples to output pinf->samplesout.Add(workbuf2,sz*sizeof(WDL_FFT_REAL)); pinf2->samplesout.Add(workbuf2+m_fft_size*2,sz*sizeof(WDL_FFT_REAL)); } else { int s=sz/2; while (s--) { p1o[0] = p1[0]+olhist[0]; p1o[1] = p1[2]+olhist[1]; p1o+=2; p1+=4; olhist[0]=p3[0]; olhist[1]=p3[2]; p3+=4; olhist+=2; } // add samples to output pinf->samplesout.Add(workbuf2,sz*sizeof(WDL_FFT_REAL)); } } // while available if (mono_impulse_mode) ch++; } int mv = want; for (ch=0;chsamplesout.Available()/sizeof(WDL_FFT_REAL) : 0; if (!ch || vsamplesout.Get(); return ret; } void WDL_ConvolutionEngine::Advance(int len) { for (int ch = 0; ch < m_proc_nch; ch ++) { ProcChannelInfo *pinf = m_proc.Get(ch); pinf->samplesout.Advance(len*sizeof(WDL_FFT_REAL)); pinf->samplesout.Compact(); } } /**************************************************************** ** low latency version */ WDL_ConvolutionEngine_Div::WDL_ConvolutionEngine_Div() { timingInit(); for (int x = 0; x < 2; x ++) m_sout.Add(new WDL_Queue); m_need_feedsilence=true; } int WDL_ConvolutionEngine_Div::SetImpulse(WDL_ImpulseBuffer *impulse, int maxfft_size, int known_blocksize, int max_imp_size, int impulse_offset, int latency_allowed) { m_need_feedsilence=true; m_engines.Empty(true); if (maxfft_size<0)maxfft_size=-maxfft_size; maxfft_size*=2; if (!maxfft_size || maxfft_size>32768) maxfft_size=32768; const int MAX_SIZE_FOR_BRUTE=64; int fftsize = MAX_SIZE_FOR_BRUTE; int impulsechunksize = MAX_SIZE_FOR_BRUTE; if (known_blocksize && !(known_blocksize&(known_blocksize-1)) && known_blocksize>MAX_SIZE_FOR_BRUTE*2) { fftsize=known_blocksize/2; impulsechunksize=known_blocksize/2; } if (latency_allowed*2 > fftsize) { int x = 16; while (x <= latency_allowed) x*=2; if (x>32768) x=32768; fftsize=impulsechunksize=x; } int offs=0; int samplesleft=impulse->impulses[0].GetSize()-impulse_offset; if (max_imp_size>0 && samplesleft>max_imp_size) samplesleft=max_imp_size; do { WDL_ConvolutionEngine *eng=new WDL_ConvolutionEngine; bool wantBrute = !latency_allowed && !offs; if (impulsechunksize*(wantBrute ? 2 : 3) >= samplesleft) impulsechunksize=samplesleft; // early-out, no point going to a larger FFT (since if we did this, we wouldnt have enough samples for a complete next pass) if (fftsize>=maxfft_size) { impulsechunksize=samplesleft; fftsize=maxfft_size; } // if FFTs are as large as possible, finish up eng->SetImpulse(impulse,fftsize,offs+impulse_offset,impulsechunksize, wantBrute); eng->m_zl_delaypos = offs; eng->m_zl_dumpage=0; m_engines.Add(eng); #ifdef WDLCONVO_ZL_ACCOUNTING wdl_log("ce%d: offs=%d, len=%d, fftsize=%d\n",m_engines.GetSize(),offs,impulsechunksize,fftsize); #endif samplesleft -= impulsechunksize; offs+=impulsechunksize; #if 1 // this seems about 10% faster (maybe due to better cache use from less sized ffts used?) impulsechunksize=offs*3; fftsize=offs*2; #else impulsechunksize=fftsize; fftsize*=2; #endif } while (samplesleft > 0); return GetLatency(); } int WDL_ConvolutionEngine_Div::GetLatency() { return m_engines.GetSize() ? m_engines.Get(0)->GetLatency() : 0; } void WDL_ConvolutionEngine_Div::Reset() { int x; for (x = 0; x < m_engines.GetSize(); x ++) { WDL_ConvolutionEngine *eng=m_engines.Get(x); eng->Reset(); } for (x = 0; x < m_sout.GetSize(); x ++) { m_sout.Get(x)->Clear(); } m_need_feedsilence=true; } WDL_ConvolutionEngine_Div::~WDL_ConvolutionEngine_Div() { timingPrint(); m_engines.Empty(true); m_sout.Empty(true); } void WDL_ConvolutionEngine_Div::Add(WDL_FFT_REAL **bufs, int len, int nch) { while (m_sout.GetSize() < nch) m_sout.Add(new WDL_Queue); while (m_sout.GetSize() > nch) m_sout.Delete(m_sout.GetSize()-1,true); bool ns=m_need_feedsilence; m_need_feedsilence=false; int x; for (x = 0; x < m_engines.GetSize(); x ++) { WDL_ConvolutionEngine *eng=m_engines.Get(x); if (ns) { eng->m_zl_dumpage = (x>0 && x < m_engines.GetSize()-1) ? (eng->GetLatency()/4) : 0; // reduce max number of ffts per block by staggering them if (eng->m_zl_dumpage>0) eng->Add(NULL,eng->m_zl_dumpage,nch); // added silence to input (to control when fft happens) } eng->Add(bufs,len,nch); if (ns) eng->AddSilenceToOutput(eng->m_zl_delaypos); // add silence to output (to delay output to its correct time) } } WDL_FFT_REAL **WDL_ConvolutionEngine_Div::Get() { WDL_FFT_REAL **ret = m_get_tmpptrs.ResizeOK(m_sout.GetSize(),false); if (WDL_NORMALLY(ret)) for (int x = 0; x < m_sout.GetSize(); x ++) ret[x]=(WDL_FFT_REAL *)m_sout.Get(x)->Get(); return ret; } void WDL_ConvolutionEngine_Div::Advance(int len) { int x; for (x = 0; x < m_sout.GetSize(); x ++) { WDL_Queue *q = m_sout.Get(x); q->Advance(len*sizeof(WDL_FFT_REAL)); q->Compact(); } } int WDL_ConvolutionEngine_Div::Avail(int wantSamples) { timingEnter(1); int wso=wantSamples; int x; #ifdef WDLCONVO_ZL_ACCOUNTING int cnt=0; static int maxcnt=-1; int h=0; #endif for (x = 0; x < m_engines.GetSize(); x ++) { WDL_ConvolutionEngine *eng=m_engines.Get(x); #ifdef WDLCONVO_ZL_ACCOUNTING eng->m_zl_fftcnt=0; #endif int a=eng->Avail(wso+eng->m_zl_dumpage) - eng->m_zl_dumpage; #ifdef WDLCONVO_ZL_ACCOUNTING cnt += !!eng->m_zl_fftcnt; #if 0 if (eng->m_zl_fftcnt) h|=1<m_zl_fftcnt && x==m_engines.GetSize()-1 && cnt>1) { wdl_log("fft flags=%08x (%08x=max)\n",h,1<maxcnt)maxcnt=cnt; if (GetTickCount()>lastt+1000) { lastt=GetTickCount(); wdl_log("maxcnt=%d\n",maxcnt); maxcnt=-1; } #endif if (wantSamples>0) { const int add_sz = wantSamples*sizeof(WDL_FFT_REAL); for (x =0; x < m_sout.GetSize(); x ++) { void *add = m_sout.Get(x)->Add(NULL,add_sz); if (WDL_NORMALLY(add != NULL)) memset(add,0,add_sz); } for (x = 0; x < m_engines.GetSize(); x ++) { WDL_ConvolutionEngine *eng=m_engines.Get(x); if (eng->m_zl_dumpage>0) { eng->Advance(eng->m_zl_dumpage); eng->m_zl_dumpage=0; } WDL_FFT_REAL **p=eng->Get(); if (p) { int i; for (i =0; i < m_sout.GetSize(); i ++) { WDL_Queue *q = m_sout.Get(i); const int qsz = q->Available(); if (WDL_NORMALLY(qsz >= add_sz)) { WDL_FFT_REAL *o=(WDL_FFT_REAL *)((char *)q->Get() + qsz - add_sz); const WDL_FFT_REAL *in=p[i]; int j=wantSamples; while (j-->0) *o++ += *in++; } } } eng->Advance(wantSamples); } } timingLeave(1); WDL_Queue *q0 = m_sout.Get(0); int av=WDL_NORMALLY(q0 != NULL) ? (int) (q0->Available()/sizeof(WDL_FFT_REAL)) : 0; return av>wso ? wso : av; } #ifdef WDL_TEST_CONVO #include int main(int argc, char **argv) { if (argc!=5) { printf("usage: convoengine fftsize implen oneoffs pingoffs\n"); return -1; } int fftsize=atoi(argv[1]); int implen = atoi(argv[2]); int oneoffs = atoi(argv[3]); int pingoffs=atoi(argv[4]); if (implen < 1 || oneoffs < 0 || oneoffs >= implen || pingoffs < 0) { printf("invalid parameters\n"); return -1; } WDL_ImpulseBuffer imp; imp.nch=1; memset(imp.impulses[0].Resize(implen),0,implen*sizeof(WDL_FFT_REAL)); imp.impulses[0].Get()[oneoffs]=1.0; #if WDL_TEST_CONVO==2 WDL_ConvolutionEngine_Div engine; #else WDL_ConvolutionEngine engine; #endif engine.SetImpulse(&imp,fftsize); WDL_TypedBuf m_tmpbuf; memset(m_tmpbuf.Resize(pingoffs+1),0,pingoffs*sizeof(WDL_FFT_REAL)); m_tmpbuf.Get()[pingoffs]=1.0; WDL_FFT_REAL *p=m_tmpbuf.Get(); engine.Add(&p,pingoffs+1,1); p=m_tmpbuf.Resize(4096); memset(p,0,m_tmpbuf.GetSize()*sizeof(WDL_FFT_REAL)); int avail; while ((avail=engine.Avail(pingoffs+oneoffs + 8192)) < pingoffs+oneoffs + 8192) { engine.Add(&p,4096,1); } WDL_FFT_REAL **output = engine.Get(); if (!output || !*output) { printf("cant get output\n"); return -1; } int x; for (x = 0; x < avail; x ++) { WDL_FFT_REAL val=output[0][x]; WDL_FFT_REAL expval = (x==pingoffs+oneoffs) ? 1.0:0.0; if (fabs(val-expval)>0.000000001) { printf("%d: %.4fdB - %f %f\n",x,log10(max(val,0.000000000001))*20.0 - log10(max(expval,0.000000000001))*20.0,val,expval); } } return 0; } #endif int WDL_ImpulseBuffer::SetLength(int samples) { const int nch = impulses.list.GetSize(); if (!nch) return 0; for (int x=0;x old_nch) { while (impulses.list.GetSize() < usench) impulses.list.Add(new WDL_TypedBuf); const int len = SetLength(GetLength()); int x,ax=0; if (duplicateExisting && len>0 && old_nch>0) for(x=old_nch;x=old_nch) ax=0; } } else while (usench