diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index 7f0949dd..d520835b 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -363,10 +363,11 @@ void FurnaceGUI::drawChanOsc() { std::vector oscChans; int chans=e->getTotalChannelCount(); ImGuiWindow* window=ImGui::GetCurrentWindow(); - ImVec2 waveform[512]; ImGuiStyle& style=ImGui::GetStyle(); + ImVec2 waveform[1024]; + // fill buffers for (int i=0; igetOscBuffer(i); if (buf!=NULL && e->curSubSong->chanShow[i]) { @@ -376,6 +377,192 @@ void FurnaceGUI::drawChanOsc() { } } + // process + for (size_t i=0; iready) { + logD("creating FFT plan for channel %d",ch); + fft->inBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double)); + fft->outBuf=(fftw_complex*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(fftw_complex)); + fft->corrBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double)); + fft->plan=fftw_plan_dft_r2c_1d(FURNACE_FFT_SIZE,fft->inBuf,fft->outBuf,FFTW_ESTIMATE); + fft->planI=fftw_plan_dft_c2r_1d(FURNACE_FFT_SIZE,fft->outBuf,fft->corrBuf,FFTW_ESTIMATE); + if (fft->plan==NULL) { + logE("failed to create plan!"); + } else if (fft->planI==NULL) { + logE("failed to create inverse plan!"); + } else if (fft->inBuf==NULL || fft->outBuf==NULL || fft->corrBuf==NULL) { + logE("failed to create FFT buffers"); + } else { + fft->ready=true; + } + } + + if (fft->ready && e->isRunning()) { + // the STRATEGY + // 1. FFT of windowed signal + // 2. inverse FFT of auto-correlation + // 3. find size of one period + // 4. DFT of the fundamental of ONE PERIOD + // 5. now we can get phase information + // + // I have a feeling this could be simplified to two FFTs or even one... + // if you know how, please tell me + + // initialization + double phase=0.0; + float minLevel=1.0f; + float maxLevel=-1.0f; + float dcOff=0.0f; + bool loudEnough=false; + + fft->needle=buf->needle; + + // first FFT + for (int j=0; jinBuf[j]=(double)buf->data[(unsigned short)(fft->needle-displaySize*2+((j*displaySize*2)/(FURNACE_FFT_SIZE)))]/32768.0; + if (fft->inBuf[j]>0.001 || fft->inBuf[j]<-0.001) loudEnough=true; + fft->inBuf[j]*=0.55-0.45*cos(M_PI*(double)j/(double)(FURNACE_FFT_SIZE>>1)); + } + + // only proceed if not quiet + if (loudEnough) { + fftw_execute(fft->plan); + + // auto-correlation and second FFT + for (int j=0; joutBuf[j][0]/=FURNACE_FFT_SIZE; + fft->outBuf[j][1]/=FURNACE_FFT_SIZE; + fft->outBuf[j][0]=fft->outBuf[j][0]*fft->outBuf[j][0]+fft->outBuf[j][1]*fft->outBuf[j][1]; + fft->outBuf[j][1]=0; + } + fft->outBuf[0][0]=0; + fft->outBuf[0][1]=0; + fft->outBuf[1][0]=0; + fft->outBuf[1][1]=0; + fftw_execute(fft->planI); + + // window + for (int j=0; j<(FURNACE_FFT_SIZE>>1); j++) { + fft->corrBuf[j]*=1.0-((double)j/(double)(FURNACE_FFT_SIZE<<1)); + } + + // find size of period + double waveLen=FURNACE_FFT_SIZE-1; + double waveLenCandL=DBL_MAX; + double waveLenCandH=DBL_MIN; + int waveLenBottom=0; + int waveLenTop=0; + + // find lowest point + for (int j=(FURNACE_FFT_SIZE>>2); j>2; j--) { + if (fft->corrBuf[j]corrBuf[j]; + waveLenBottom=j; + } + } + + // find highest point + for (int j=(FURNACE_FFT_SIZE>>1)-1; j>waveLenBottom; j--) { + if (fft->corrBuf[j]>waveLenCandH) { + waveLenCandH=fft->corrBuf[j]; + waveLen=j; + } + } + waveLenTop=waveLen; + + // did we find the period size? + if (waveLen<(FURNACE_FFT_SIZE-32)) { + // we got pitch + chanOscPitch[ch]=pow(1.0-(waveLen/(double)(FURNACE_FFT_SIZE>>1)),4.0); + + waveLen*=(double)displaySize*2.0/(double)FURNACE_FFT_SIZE; + + // DFT of one period (x_1) + double dft[2]; + dft[0]=0.0; + dft[1]=0.0; + for (int j=fft->needle-1-(displaySize>>1)-(int)waveLen, k=0; kdata[j&0xffff]/32768.0); + double two=(double)k*(-2.0*M_PI)/waveLen; + dft[0]+=one*cos(two); + dft[1]+=one*sin(two); + } + + // calculate and lock into phase + phase=(0.5+(atan2(dft[1],dft[0])/(2.0*M_PI))); + + if (chanOscWaveCorr) { + fft->needle-=phase*waveLen; + } + } + + // FFT debug code! + if (debugFFT) { + double maxavg=0.0; + for (unsigned short j=0; j<(FURNACE_FFT_SIZE>>1); j++) { + if (fabs(fft->corrBuf[j]>maxavg)) { + maxavg=fabs(fft->corrBuf[j]); + } + } + if (maxavg>0.0000001) maxavg=0.5/maxavg; + + for (unsigned short j=0; jcorrBuf[(j*FURNACE_FFT_SIZE)/precision]*maxavg; + if (j>=precision/2) { + y=fft->inBuf[((j-(precision/2))*FURNACE_FFT_SIZE*2)/(precision)]; + } + + waveform[j]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); + } + String cPhase=fmt::sprintf("\n%.1f (b: %d t: %d)",waveLen,waveLenBottom,waveLenTop); + dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); + + dl->AddLine( + ImLerp(inRect.Min,inRect.Max,ImVec2((double)waveLenBottom/(double)FURNACE_FFT_SIZE,0.0)), + ImLerp(inRect.Min,inRect.Max,ImVec2((double)waveLenBottom/(double)FURNACE_FFT_SIZE,1.0)), + 0xffffff00 + ); + dl->AddLine( + ImLerp(inRect.Min,inRect.Max,ImVec2((double)waveLenTop/(double)FURNACE_FFT_SIZE,0.0)), + ImLerp(inRect.Min,inRect.Max,ImVec2((double)waveLenTop/(double)FURNACE_FFT_SIZE,1.0)), + 0xff00ff00 + ); + } + } else { + if (debugFFT) { + dl->AddText(inRect.Min,0xffffffff,"\nquiet"); + } + } + + if (!debugFFT || !loudEnough) { + fft->needle-=displaySize; + for (unsigned short j=0; jdata[(unsigned short)(fft->needle+(j*displaySize/precision))]/32768.0f; + if (minLevel>y) minLevel=y; + if (maxLeveldata[(unsigned short)(fft->needle+(j*displaySize/precision))]/32768.0f; + y-=dcOff; + if (y<-0.5f) y=-0.5f; + if (y>0.5f) y=0.5f; + y*=chanOscAmplify; + waveform[j]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); + } + } + } + } + } + // 0: none // 1: sqrt(chans) // 2: sqrt(chans+1) @@ -396,6 +583,7 @@ void FurnaceGUI::drawChanOsc() { int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols; + // render for (size_t i=0; ireadNeedle=buf->needle; - } - - // check FFT status existence - if (fft->plan==NULL) { - logD("creating FFT plan for channel %d",ch); - fft->inBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double)); - fft->outBuf=(fftw_complex*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(fftw_complex)); - fft->corrBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double)); - fft->plan=fftw_plan_dft_r2c_1d(FURNACE_FFT_SIZE,fft->inBuf,fft->outBuf,FFTW_ESTIMATE); - fft->planI=fftw_plan_dft_c2r_1d(FURNACE_FFT_SIZE,fft->outBuf,fft->corrBuf,FFTW_ESTIMATE); - if (fft->plan==NULL) { - logE("failed to create plan!"); - } - if (fft->planI==NULL) { - logE("failed to create inverse plan!"); - } - if (fft->inBuf==NULL || fft->outBuf==NULL || fft->corrBuf==NULL) { - logE("failed to create FFT buffers"); - } - } - - int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f); - ImVec2 minArea=window->DC.CursorPos; ImVec2 maxArea=ImVec2( minArea.x+size.x, @@ -448,7 +611,13 @@ void FurnaceGUI::drawChanOsc() { int precision=inRect.Max.x-inRect.Min.x; if (precision<1) precision=1; - if (precision>512) precision=512; + if (precision>1024) precision=1024; + + if (centerSettingReset) { + buf->readNeedle=buf->needle; + } + + int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f); ImGui::ItemSize(size,style.FramePadding.y); if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) { @@ -458,161 +627,6 @@ void FurnaceGUI::drawChanOsc() { waveform[j]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f)); } } else { - // the STRATEGY - // 1. FFT of windowed signal - // 2. inverse FFT of auto-correlation - // 3. find size of one period - // 4. DFT of the fundamental of ONE PERIOD - // 5. now we can get phase information - // - // I have a feeling this could be simplified to two FFTs or even one... - // if you know how, please tell me - - // initialization - double phase=0.0; - float minLevel=1.0f; - float maxLevel=-1.0f; - float dcOff=0.0f; - unsigned short needlePos=buf->needle; - bool loudEnough=false; - - // first FFT - for (int j=0; jinBuf[j]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((j*displaySize*2)/(FURNACE_FFT_SIZE)))]/32768.0; - if (fft->inBuf[j]>0.001 || fft->inBuf[j]<-0.001) loudEnough=true; - fft->inBuf[j]*=0.55-0.45*cos(M_PI*(double)j/(double)(FURNACE_FFT_SIZE>>1)); - } - - // only proceed if not quiet - if (loudEnough) { - fftw_execute(fft->plan); - - // auto-correlation and second FFT - for (int j=0; joutBuf[j][0]/=FURNACE_FFT_SIZE; - fft->outBuf[j][1]/=FURNACE_FFT_SIZE; - fft->outBuf[j][0]=fft->outBuf[j][0]*fft->outBuf[j][0]+fft->outBuf[j][1]*fft->outBuf[j][1]; - fft->outBuf[j][1]=0; - } - fft->outBuf[0][0]=0; - fft->outBuf[0][1]=0; - fft->outBuf[1][0]=0; - fft->outBuf[1][1]=0; - fftw_execute(fft->planI); - - // window - for (int j=0; j<(FURNACE_FFT_SIZE>>1); j++) { - fft->corrBuf[j]*=1.0-((double)j/(double)(FURNACE_FFT_SIZE<<1)); - } - - // find size of period - double waveLen=FURNACE_FFT_SIZE-1; - double waveLenCandL=DBL_MAX; - double waveLenCandH=DBL_MIN; - int waveLenBottom=0; - int waveLenTop=0; - - // find lowest point - for (int j=(FURNACE_FFT_SIZE>>2); j>2; j--) { - if (fft->corrBuf[j]corrBuf[j]; - waveLenBottom=j; - } - } - - // find highest point - for (int j=(FURNACE_FFT_SIZE>>1)-1; j>waveLenBottom; j--) { - if (fft->corrBuf[j]>waveLenCandH) { - waveLenCandH=fft->corrBuf[j]; - waveLen=j; - } - } - waveLenTop=waveLen; - - // did we find the period size? - if (waveLen<(FURNACE_FFT_SIZE-32)) { - // we got pitch - chanOscPitch[ch]=pow(1.0-(waveLen/(double)(FURNACE_FFT_SIZE>>1)),4.0); - - waveLen*=(double)displaySize*2.0/(double)FURNACE_FFT_SIZE; - - // DFT of one period (x_1) - double dft[2]; - dft[0]=0.0; - dft[1]=0.0; - for (int j=needlePos-1-(displaySize>>1)-(int)waveLen, k=0; kdata[j&0xffff]/32768.0); - double two=(double)k*(-2.0*M_PI)/waveLen; - dft[0]+=one*cos(two); - dft[1]+=one*sin(two); - } - - // calculate and lock into phase - phase=(0.5+(atan2(dft[1],dft[0])/(2.0*M_PI))); - - if (chanOscWaveCorr) { - needlePos-=phase*waveLen; - //needlePos-=(2*waveLen-fmod(displaySize,waveLen*2))*0.5; - } - } - - // FFT debug code! - if (debugFFT) { - double maxavg=0.0; - for (unsigned short j=0; j<(FURNACE_FFT_SIZE>>1); j++) { - if (fabs(fft->corrBuf[j]>maxavg)) { - maxavg=fabs(fft->corrBuf[j]); - } - } - if (maxavg>0.0000001) maxavg=0.5/maxavg; - - for (unsigned short j=0; jcorrBuf[(j*FURNACE_FFT_SIZE)/precision]*maxavg; - if (j>=precision/2) { - y=fft->inBuf[((j-(precision/2))*FURNACE_FFT_SIZE*2)/(precision)]; - } - - waveform[j]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); - } - String cPhase=fmt::sprintf("\n%.1f (b: %d t: %d)",waveLen,waveLenBottom,waveLenTop); - dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); - - dl->AddLine( - ImLerp(inRect.Min,inRect.Max,ImVec2((double)waveLenBottom/(double)FURNACE_FFT_SIZE,0.0)), - ImLerp(inRect.Min,inRect.Max,ImVec2((double)waveLenBottom/(double)FURNACE_FFT_SIZE,1.0)), - 0xffffff00 - ); - dl->AddLine( - ImLerp(inRect.Min,inRect.Max,ImVec2((double)waveLenTop/(double)FURNACE_FFT_SIZE,0.0)), - ImLerp(inRect.Min,inRect.Max,ImVec2((double)waveLenTop/(double)FURNACE_FFT_SIZE,1.0)), - 0xff00ff00 - ); - } - } else { - if (debugFFT) { - dl->AddText(inRect.Min,0xffffffff,"\nquiet"); - } - } - - if (!debugFFT || !loudEnough) { - needlePos-=displaySize; - for (unsigned short j=0; jdata[(unsigned short)(needlePos+(j*displaySize/precision))]/32768.0f; - if (minLevel>y) minLevel=y; - if (maxLeveldata[(unsigned short)(needlePos+(j*displaySize/precision))]/32768.0f; - y-=dcOff; - if (y<-0.5f) y=-0.5f; - if (y>0.5f) y=0.5f; - y*=chanOscAmplify; - waveform[j]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); - } - } } ImU32 color=ImGui::GetColorU32(chanOscColor); if (chanOscUseGrad) { diff --git a/src/gui/gui.h b/src/gui/gui.h index b28918fd..37fcd4a3 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2056,20 +2056,22 @@ class FurnaceGUI { unsigned short lastCorrPos[DIV_MAX_CHANS]; struct ChanOscStatus { double* inBuf; + fftw_complex* outBuf; + double* corrBuf; size_t inBufPos; double inBufPosFrac; unsigned short needle; - fftw_complex* outBuf; - double* corrBuf; + bool ready; fftw_plan plan; fftw_plan planI; ChanOscStatus(): inBuf(NULL), + outBuf(NULL), + corrBuf(NULL), inBufPos(0), inBufPosFrac(0.0f), needle(0), - outBuf(NULL), - corrBuf(NULL), + ready(false), plan(NULL), planI(NULL) {} } chanOscChan[DIV_MAX_CHANS];