diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index cb76baf0a..9d2230ae6 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -24,9 +24,9 @@ #include "imgui_internal.h" #include "misc/cpp/imgui_stdlib.h" -#define FURNACE_FFT_SIZE 4096 -#define FURNACE_FFT_RATE 80.0 -#define FURNACE_FFT_CUTOFF 0.1 +#define FURNACE_CHANOSC_FFT_SIZE 4096 +#define FURNACE_CHANOSC_FFT_RATE 80.0 +#define FURNACE_CHANOSC_FFT_CUTOFF 0.1 const char* chanOscRefs[]={ _N("None (0%)"), @@ -442,11 +442,11 @@ void FurnaceGUI::drawChanOsc() { // check FFT status existence if (!fft_->ready) { logD(_("creating FFT plan for channel %d"),fft_->relatedCh); - 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); + fft_->inBuf=(double*)fftw_malloc(FURNACE_CHANOSC_FFT_SIZE*sizeof(double)); + fft_->outBuf=(fftw_complex*)fftw_malloc(FURNACE_CHANOSC_FFT_SIZE*sizeof(fftw_complex)); + fft_->corrBuf=(double*)fftw_malloc(FURNACE_CHANOSC_FFT_SIZE*sizeof(double)); + fft_->plan=fftw_plan_dft_r2c_1d(FURNACE_CHANOSC_FFT_SIZE,fft_->inBuf,fft_->outBuf,FFTW_ESTIMATE); + fft_->planI=fftw_plan_dft_c2r_1d(FURNACE_CHANOSC_FFT_SIZE,fft_->outBuf,fft_->corrBuf,FFTW_ESTIMATE); if (fft_->plan==NULL) { logE(_("failed to create plan!")); } else if (fft_->planI==NULL) { @@ -485,24 +485,24 @@ void FurnaceGUI::drawChanOsc() { // first FFT int k=0; short lastSample=0; - memset(fft->inBuf,0,FURNACE_FFT_SIZE*sizeof(double)); - if (displaySize2data[(unsigned short)(fft->needle-displaySize2+((j*displaySize2)/(FURNACE_FFT_SIZE)))]; + memset(fft->inBuf,0,FURNACE_CHANOSC_FFT_SIZE*sizeof(double)); + if (displaySize2data[(unsigned short)(fft->needle-displaySize2+((j*displaySize2)/(FURNACE_CHANOSC_FFT_SIZE)))]; if (newData!=-1) lastSample=newData; if (j<0) continue; fft->inBuf[j]=(double)lastSample/32768.0; if (fft->inBuf[j]>0.001 || fft->inBuf[j]<-0.001) fft->loudEnough=true; - fft->inBuf[j]*=0.55-0.45*cos(M_PI*(double)j/(double)(FURNACE_FFT_SIZE>>1)); + fft->inBuf[j]*=0.55-0.45*cos(M_PI*(double)j/(double)(FURNACE_CHANOSC_FFT_SIZE>>1)); } } else { for (unsigned short j=fft->needle-displaySize2; j!=fft->needle; j++, k++) { - const int kIn=(k*FURNACE_FFT_SIZE)/displaySize2; - if (kIn>=FURNACE_FFT_SIZE) break; + const int kIn=(k*FURNACE_CHANOSC_FFT_SIZE)/displaySize2; + if (kIn>=FURNACE_CHANOSC_FFT_SIZE) break; if (buf->data[j]!=-1) lastSample=buf->data[j]; fft->inBuf[kIn]=(double)lastSample/32768.0; if (fft->inBuf[kIn]>0.001 || fft->inBuf[kIn]<-0.001) fft->loudEnough=true; - fft->inBuf[kIn]*=0.55-0.45*cos(M_PI*(double)kIn/(double)(FURNACE_FFT_SIZE>>1)); + fft->inBuf[kIn]*=0.55-0.45*cos(M_PI*(double)kIn/(double)(FURNACE_CHANOSC_FFT_SIZE>>1)); } } @@ -511,9 +511,9 @@ void FurnaceGUI::drawChanOsc() { 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; + for (int j=0; joutBuf[j][0]/=FURNACE_CHANOSC_FFT_SIZE; + fft->outBuf[j][1]/=FURNACE_CHANOSC_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; } @@ -524,19 +524,19 @@ void FurnaceGUI::drawChanOsc() { 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)); + for (int j=0; j<(FURNACE_CHANOSC_FFT_SIZE>>1); j++) { + fft->corrBuf[j]*=1.0-((double)j/(double)(FURNACE_CHANOSC_FFT_SIZE<<1)); } // find size of period double waveLenCandL=DBL_MAX; double waveLenCandH=DBL_MIN; - fft->waveLen=FURNACE_FFT_SIZE-1; + fft->waveLen=FURNACE_CHANOSC_FFT_SIZE-1; fft->waveLenBottom=0; fft->waveLenTop=0; // find lowest point - for (int j=(FURNACE_FFT_SIZE>>2); j>2; j--) { + for (int j=(FURNACE_CHANOSC_FFT_SIZE>>2); j>2; j--) { if (fft->corrBuf[j]corrBuf[j]; fft->waveLenBottom=j; @@ -544,7 +544,7 @@ void FurnaceGUI::drawChanOsc() { } // find highest point - for (int j=(FURNACE_FFT_SIZE>>1)-1; j>fft->waveLenBottom; j--) { + for (int j=(FURNACE_CHANOSC_FFT_SIZE>>1)-1; j>fft->waveLenBottom; j--) { if (fft->corrBuf[j]>waveLenCandH) { waveLenCandH=fft->corrBuf[j]; fft->waveLen=j; @@ -553,11 +553,11 @@ void FurnaceGUI::drawChanOsc() { fft->waveLenTop=fft->waveLen; // did we find the period size? - if (fft->waveLen<(FURNACE_FFT_SIZE-32)) { + if (fft->waveLen<(FURNACE_CHANOSC_FFT_SIZE-32)) { // we got pitch - fft->pitch=pow(1.0-(fft->waveLen/(double)(FURNACE_FFT_SIZE>>1)),4.0); + fft->pitch=pow(1.0-(fft->waveLen/(double)(FURNACE_CHANOSC_FFT_SIZE>>1)),4.0); - fft->waveLen*=(double)displaySize*2.0/(double)FURNACE_FFT_SIZE; + fft->waveLen*=(double)displaySize*2.0/(double)FURNACE_CHANOSC_FFT_SIZE; // DFT of one period (x_1) double dft[2]; @@ -662,7 +662,7 @@ void FurnaceGUI::drawChanOsc() { if (debugFFT) { // FFT debug code! double maxavg=0.0; - for (unsigned short j=0; j<(FURNACE_FFT_SIZE>>1); j++) { + for (unsigned short j=0; j<(FURNACE_CHANOSC_FFT_SIZE>>1); j++) { if (fabs(fft->corrBuf[j]>maxavg)) { maxavg=fabs(fft->corrBuf[j]); } @@ -673,9 +673,9 @@ void FurnaceGUI::drawChanOsc() { for (unsigned short j=0; j=precision/2) { - y=fft->inBuf[((j-(precision/2))*FURNACE_FFT_SIZE*2)/(precision)]; + y=fft->inBuf[((j-(precision/2))*FURNACE_CHANOSC_FFT_SIZE*2)/(precision)]; } else { - y=fft->corrBuf[(j*FURNACE_FFT_SIZE)/precision]*maxavg; + y=fft->corrBuf[(j*FURNACE_CHANOSC_FFT_SIZE)/precision]*maxavg; } fft->oscTex[j]=y*2.0; } @@ -684,25 +684,25 @@ void FurnaceGUI::drawChanOsc() { float x=(float)j/(float)precision; float y; if (j>=precision/2) { - y=fft->inBuf[((j-(precision/2))*FURNACE_FFT_SIZE*2)/(precision)]; + y=fft->inBuf[((j-(precision/2))*FURNACE_CHANOSC_FFT_SIZE*2)/(precision)]; } else { - y=fft->corrBuf[(j*FURNACE_FFT_SIZE)/precision]*maxavg; + y=fft->corrBuf[(j*FURNACE_CHANOSC_FFT_SIZE)/precision]*maxavg; } waveform[j]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); } } if (fft->loudEnough) { - String cPhase=fmt::sprintf("\n%.1f (b: %d t: %d)\nSIZES: %d, %d, %d\nPHASE %f",fft->waveLen,fft->waveLenBottom,fft->waveLenTop,displaySize,displaySize2,FURNACE_FFT_SIZE,fft->debugPhase); + String cPhase=fmt::sprintf("\n%.1f (b: %d t: %d)\nSIZES: %d, %d, %d\nPHASE %f",fft->waveLen,fft->waveLenBottom,fft->waveLenTop,displaySize,displaySize2,FURNACE_CHANOSC_FFT_SIZE,fft->debugPhase); dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); dl->AddLine( - ImLerp(inRect.Min,inRect.Max,ImVec2((double)fft->waveLenBottom/(double)FURNACE_FFT_SIZE,0.0)), - ImLerp(inRect.Min,inRect.Max,ImVec2((double)fft->waveLenBottom/(double)FURNACE_FFT_SIZE,1.0)), + ImLerp(inRect.Min,inRect.Max,ImVec2((double)fft->waveLenBottom/(double)FURNACE_CHANOSC_FFT_SIZE,0.0)), + ImLerp(inRect.Min,inRect.Max,ImVec2((double)fft->waveLenBottom/(double)FURNACE_CHANOSC_FFT_SIZE,1.0)), 0xffffff00 ); dl->AddLine( - ImLerp(inRect.Min,inRect.Max,ImVec2((double)fft->waveLenTop/(double)FURNACE_FFT_SIZE,0.0)), - ImLerp(inRect.Min,inRect.Max,ImVec2((double)fft->waveLenTop/(double)FURNACE_FFT_SIZE,1.0)), + ImLerp(inRect.Min,inRect.Max,ImVec2((double)fft->waveLenTop/(double)FURNACE_CHANOSC_FFT_SIZE,0.0)), + ImLerp(inRect.Min,inRect.Max,ImVec2((double)fft->waveLenTop/(double)FURNACE_CHANOSC_FFT_SIZE,1.0)), 0xff00ff00 ); dl->AddLine( diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2ad3bae8e..d031c24ff 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -98,6 +98,18 @@ void FurnaceGUI::enableSafeMode() { safeMode=true; } +const char* FurnaceGUI::noteName(short note) { + if (note<0 || note>=180) { + return "???"; + } + if (settings.flatNotes) { + if (settings.germanNotation) return noteNamesGF[note]; + return noteNamesF[note]; + } + if (settings.germanNotation) return noteNamesG[note]; + return noteNames[note]; +} + const char* FurnaceGUI::noteName(short note, short octave) { if (note==100) { return noteOffLabel; @@ -8395,6 +8407,23 @@ bool FurnaceGUI::finish(bool saveConfig) { delete[] opTouched; opTouched=NULL; + if (tunerFFTInBuf) { + delete[] tunerFFTInBuf; + tunerFFTInBuf=NULL; + } + if (tunerFFTOutBuf) { + fftw_free(tunerFFTOutBuf); + tunerFFTOutBuf=NULL; + } + if (spectrumPlan) { + fftw_free(spectrumPlan); + spectrumPlan=NULL; + } + if (tunerPlan) { + fftw_free(tunerPlan); + tunerPlan=NULL; + } + return true; } @@ -8929,6 +8958,9 @@ FurnaceGUI::FurnaceGUI(): xyOscDecayTime(10.0f), xyOscIntensity(2.0f), xyOscThickness(2.0f), + spectrumPlan(NULL), + tunerPlan(NULL), + spectrumBins(2048), followLog(true), #ifdef IS_MOBILE pianoOctaves(7), diff --git a/src/gui/gui.h b/src/gui/gui.h index b43ef5849..c2a4c818d 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2705,6 +2705,12 @@ class FurnaceGUI { float xyOscIntensity; float xyOscThickness; + // spectrum and tuner + double* tunerFFTInBuf; + fftw_complex* tunerFFTOutBuf; + fftw_plan spectrumPlan, tunerPlan; + int spectrumBins; + // visualizer float keyHit[DIV_MAX_CHANS]; float keyHit1[DIV_MAX_CHANS]; @@ -3121,6 +3127,7 @@ class FurnaceGUI { void showError(String what); String getLastError(); const char* noteNameNormal(short note, short octave); + const char* noteName(short note); const char* noteName(short note, short octave); bool decodeNote(const char* what, short& note, short& octave); void bindEngine(DivEngine* eng); diff --git a/src/gui/tuner.cpp b/src/gui/tuner.cpp index e090afc5b..284079336 100644 --- a/src/gui/tuner.cpp +++ b/src/gui/tuner.cpp @@ -19,14 +19,11 @@ #define _USE_MATH_DEFINES #include "gui.h" -#include "../ta-log.h" #include "imgui.h" #include "imgui_internal.h" #include "misc/cpp/imgui_stdlib.h" -#define FURNACE_FFT_SIZE 16384//4096 -#define FURNACE_FFT_RATE 80.0 -#define FURNACE_FFT_CUTOFF 0.1 +#define FURNACE_TUNER_FFT_SIZE 8192 void FurnaceGUI::drawTuner() { if (nextWindow == GUI_WINDOW_TUNER) { @@ -38,40 +35,39 @@ void FurnaceGUI::drawTuner() { if (ImGui::Begin("Tuner", &tunerOpen, globalWinFlags, _("Tuner"))) { //fft buffer - static double inBuf[FURNACE_FFT_SIZE]; - static fftw_complex outBuf[FURNACE_FFT_SIZE]; - static fftw_plan plan=nullptr; + if (!tunerFFTInBuf) tunerFFTInBuf=new double[FURNACE_TUNER_FFT_SIZE]; + if (!tunerFFTOutBuf) tunerFFTOutBuf=(fftw_complex*)fftw_malloc(sizeof(fftw_complex)*FURNACE_TUNER_FFT_SIZE); - if (!plan) { - plan=fftw_plan_dft_r2c_1d(FURNACE_FFT_SIZE, inBuf, outBuf, FFTW_ESTIMATE); + if (!tunerPlan) { + tunerPlan=fftw_plan_dft_r2c_1d(FURNACE_TUNER_FFT_SIZE, tunerFFTInBuf, tunerFFTOutBuf, FFTW_ESTIMATE); } int chans=e->getAudioDescGot().outChans; - int needle=e->oscWritePos; + int needle=e->oscReadPos; - for (int j=0; joscBuf[ch][pos]; } sample=sample/chans; - inBuf[j]=sample * (0.5 * (1.0 - cos(2.0 * M_PI * j / (FURNACE_FFT_SIZE - 1)))); + tunerFFTInBuf[j]=sample*(0.5*(1.0-cos(2.0*M_PI*j/(FURNACE_TUNER_FFT_SIZE-1)))); } - fftw_execute(plan); + fftw_execute(tunerPlan); - std::vector mag(FURNACE_FFT_SIZE / 2); - for (int k=0; k < FURNACE_FFT_SIZE / 2; k++) { - mag[k]=sqrt(outBuf[k][0]*outBuf[k][0]+outBuf[k][1]*outBuf[k][1]); + std::vector mag(FURNACE_TUNER_FFT_SIZE/2); + for (int k=0; k < FURNACE_TUNER_FFT_SIZE / 2; k++) { + mag[k]=sqrt(tunerFFTOutBuf[k][0]*tunerFFTOutBuf[k][0]+tunerFFTOutBuf[k][1]*tunerFFTOutBuf[k][1]); } //harmonic product spectrum - int harmonics = 2; - for (int h = 2; h <= harmonics; h++) { - for (int k = 0; k < FURNACE_FFT_SIZE / (2 * h); k++) { - mag[k] *= mag[k * h]; + int harmonics=2; + for (int h=2; h<=harmonics; h++) { + for (int k=0; k 0 && freq < 5000.0) { - double noteExact = log2(freq / 440.0) * 12.0 + 45.0; - int noteRounded = (int)std::round(noteExact); - - int noteInOct = noteRounded % 12; - if (noteInOct < 0) noteInOct += 12; - - int octave = noteRounded / 12; - if (noteRounded < 0 && (noteRounded % 12)) --octave; - - double cents = (noteExact - noteRounded) * 100.0; - - static const char* names[12] = { - "C","C#","D","D#","E","F","F#","G","G#","A","A#","B" - }; - - - - ImGui::Text("Note: %s%d", names[noteInOct], octave); - ImGui::Text("Freq: %.2f Hz", freq); - ImGui::Text("Cents offset: %+0.1f", cents); + noteExact=CLAMP(log2(freq / e->song.tuning) * 12.0 + 117.0,0,180); + noteRounded=round(noteExact); + cents=noteExact-noteRounded; } - else { - ImGui::Text("Note: -"); - ImGui::Text("Freq: 0.00 Hz"); - ImGui::Text("Cents offset: +0.0"); + String noteText=fmt::sprintf("%s",(freq > 0 && freq < 5000.0)?noteName(noteExact):"---"); + ImGui::Text("Note: %s", noteText.c_str()); + ImGui::Text("Freq: %f Hz", freq); + ImGui::Text("Cents: %f ", cents*100.0f); + + { + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImVec2 origin=ImGui::GetWindowPos(); + ImVec2 size=ImGui::GetWindowSize(); + float titleBar = ImGui::GetCurrentWindow()->TitleBarHeight; + origin.y+=titleBar; + size.y-=titleBar; + // debug stuff + ImVec2* plot=new ImVec2[FURNACE_TUNER_FFT_SIZE]; + for (size_t i=0; iAddPolyline(plot, FURNACE_TUNER_FFT_SIZE, 0xff00ff00, 0, 1.0f); + for (size_t i=0; iAddPolyline(plot, mag.size(), 0xffffffff, 0, 1.0f); + delete[] plot; + ImVec2 needleCenter=origin+ImVec2(size.x/2,size.y/4*3); + float needleLength = (size.x/2)*0.9f; + const float halfSpan=0.7; // radians + const float trim=.2; + float angle=cents*halfSpan; + ImVec2 needleTip = needleCenter + ImVec2( + needleLength*sin(angle), + -needleLength*cos(angle) + ); + // text + ImGui::PushFont(bigFont); + ImVec2 textSize=ImGui::CalcTextSize(noteText.c_str()); + dl->AddText(needleCenter-ImVec2(textSize.x/2.0f,textSize.y/2.0f),ImGui::ColorConvertFloat4ToU32(uiColors[GUI_COLOR_TEXT]),noteText.c_str()); + ImGui::PopFont(); + // needle + needleCenter = needleCenter+ImVec2( + needleLength*sin(angle)*trim, + -needleLength*cos(angle)*trim + ); + + dl->AddLine(needleCenter, needleTip, 0xff00ffff, 2.0f); } }