diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index d9d3ddd49..57fa99dac 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -26,6 +26,49 @@ #define FURNACE_FFT_RATE 80.0 #define FURNACE_FFT_CUTOFF 0.1 +const char* chanOscRefs[]={ + "None (0%)", + "None (50%)", + "None (100%)", + + "Frequency", + "Volume", + "Channel", + "Brightness", + + "Note Trigger" +}; + +float FurnaceGUI::computeGradPos(int type, int chan) { + switch (type) { + case GUI_OSCREF_NONE: + return 0.0f; + break; + case GUI_OSCREF_CENTER: + return 0.5f; + break; + case GUI_OSCREF_MAX: + return 1.0f; + break; + case GUI_OSCREF_FREQUENCY: + return chanOscPitch[chan]; + break; + case GUI_OSCREF_VOLUME: + return chanOscVol[chan]; + break; + case GUI_OSCREF_CHANNEL: + return (float)chan/(float)(e->getTotalChannelCount()-1); + break; + case GUI_OSCREF_BRIGHT: + return chanOscBright[chan]; + break; + case GUI_OSCREF_NOTE_TRIGGER: + return keyHit[chan]*5.0f; + break; + } + return 0.0f; +} + void FurnaceGUI::drawChanOsc() { if (nextWindow==GUI_WINDOW_CHAN_OSC) { chanOscOpen=true; @@ -36,6 +79,7 @@ void FurnaceGUI::drawChanOsc() { ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen,globalWinFlags|((chanOscOptions)?0:ImGuiWindowFlags_NoTitleBar))) { bool centerSettingReset=false; + ImDrawList* dl=ImGui::GetWindowDrawList(); if (chanOscOptions) { if (ImGui::BeginTable("ChanOscSettings",3)) { ImGui::TableNextRow(); @@ -65,60 +109,142 @@ void FurnaceGUI::drawChanOsc() { ImGui::EndTable(); } - ImGui::Text("Gradient"); - - if (chanOscGradTex==NULL) { - chanOscGradTex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,SDL_TEXTUREACCESS_STREAMING,chanOscGrad.width,chanOscGrad.height); + ImGui::Checkbox("Gradient",&chanOscUseGrad); + if (chanOscUseGrad) { if (chanOscGradTex==NULL) { - logE("error while creating gradient texture! %s",SDL_GetError()); - } else { - updateChanOscGradTex=true; + chanOscGradTex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,SDL_TEXTUREACCESS_STREAMING,chanOscGrad.width,chanOscGrad.height); + + if (chanOscGradTex==NULL) { + logE("error while creating gradient texture! %s",SDL_GetError()); + } else { + updateChanOscGradTex=true; + } } - } - if (ImGui::BeginTable("ChanOscGradSet",2)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + if (ImGui::BeginTable("ChanOscGradSet",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (chanOscGradTex!=NULL) { - if (updateChanOscGradTex) { - chanOscGrad.render(); - if (SDL_UpdateTexture(chanOscGradTex,NULL,chanOscGrad.grad.get(),chanOscGrad.width*4)==0) { - updateChanOscGradTex=false; - } else { - logE("error while updating gradient texture! %s",SDL_GetError()); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (chanOscGradTex!=NULL) { + if (updateChanOscGradTex) { + chanOscGrad.render(); + if (SDL_UpdateTexture(chanOscGradTex,NULL,chanOscGrad.grad.get(),chanOscGrad.width*4)==0) { + updateChanOscGradTex=false; + } else { + logE("error while updating gradient texture! %s",SDL_GetError()); + } + } + + ImVec2 gradLeft=ImGui::GetCursorPos(); + ImVec2 gradSize=ImVec2(400.0f*dpiScale,400.0f*dpiScale); + ImGui::Image(chanOscGradTex,gradSize); + ImVec2 gradLeftAbs=ImGui::GetItemRectMin(); + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + chanOscGrad.points.push_back(Gradient2DPoint( + (ImGui::GetMousePos().x-gradLeftAbs.x)/gradSize.x, + (ImGui::GetMousePos().y-gradLeftAbs.y)/gradSize.y + )); + updateChanOscGradTex=true; + } + + ImVec2 oldCurPos=ImGui::GetCursorPos(); + int index=0; + int removePoint=-1; + for (Gradient2DPoint& i: chanOscGrad.points) { + ImGui::PushID(index+16); + ImGui::SetCursorPos(ImVec2(gradLeft.x+i.x*gradSize.x-8.0*dpiScale,gradLeft.y+i.y*gradSize.y-8.0*dpiScale)); + if (ImGui::InvisibleButton("gradPoint",ImVec2(16.0*dpiScale,16.0*dpiScale))) { + if (!i.grab) { + ImGui::OpenPopup("gradPointSettings"); + } + } + if (ImGui::IsItemHovered() || ImGui::IsItemActive()) { + ImGui::SetTooltip("(%.1f, %.1f)",i.x*100.0f,(1.0f-i.y)*100.0f); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) { + removePoint=index; + } + if (ImGui::IsItemActive()) { + float mX=(ImGui::GetMousePos().x-gradLeftAbs.x)/gradSize.x; + float mY=(ImGui::GetMousePos().y-gradLeftAbs.y)/gradSize.y; + + if (i.grab || (fabs(i.x-mX)>0.015 || fabs(i.y-mY)>0.015)) { + i.x=mX; + i.y=mY; + i.grab=true; + + if (i.x<0) i.x=0; + if (i.x>1) i.x=1; + if (i.y<0) i.y=0; + if (i.y>1) i.y=1; + updateChanOscGradTex=true; + } + } else { + i.grab=false; + i.prevX=i.x; + i.prevY=i.y; + } + if (ImGui::BeginPopup("gradPointSettings",ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::ColorPicker4("Color",(float*)&i.color)) { + updateChanOscGradTex=true; + } + ImGui::Text("Distance"); + ImGui::SameLine(); + float pDist=i.distance*100.0f; + if (ImGui::SliderFloat("##PDistance",&pDist,0.0f,150.0f,"%.1f%%")) { + i.distance=pDist/100.0f; + updateChanOscGradTex=true; + } + + ImGui::Text("Spread"); + ImGui::SameLine(); + float pSpread=i.spread*100.0f; + if (ImGui::SliderFloat("##PSpread",&pSpread,0.0f,150.0f,"%.1f%%")) { + i.spread=pSpread/100.0f; + updateChanOscGradTex=true; + } + + if (ImGui::Button("Remove")) { + removePoint=index; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + dl->AddCircle(ImVec2(gradLeftAbs.x+i.x*gradSize.x,gradLeftAbs.y+i.y*gradSize.y),8.0*dpiScale,ImGui::ColorConvertFloat4ToU32(ImVec4(0.5,0.5,0.5,1.0)),6,2.0f*dpiScale); + dl->AddCircle(ImVec2(gradLeftAbs.x+i.x*gradSize.x,gradLeftAbs.y+i.y*gradSize.y),5.0*dpiScale,ImGui::ColorConvertFloat4ToU32(ImVec4(0.1,0.1,0.1,1.0)),6,2.0f*dpiScale); + + ImGui::PopID(); + index++; + } + ImGui::SetCursorPos(oldCurPos); + + if (removePoint>=0) { + chanOscGrad.points.erase(chanOscGrad.points.begin()+removePoint); + updateChanOscGradTex=true; } } - ImGui::ImageButton(chanOscGradTex,ImVec2(400.0f*dpiScale,400.0f*dpiScale)); - ImVec2 gradLeft=ImGui::GetItemRectMin(); - ImVec2 gradSize=ImGui::GetItemRectSize(); - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - chanOscGrad.points.push_back(Gradient2DPoint( - (ImGui::GetMousePos().x-gradLeft.x)/gradSize.x, - (ImGui::GetMousePos().y-gradLeft.y)/gradSize.y - )); + ImGui::TableNextColumn(); + if (ImGui::ColorEdit4("Background",(float*)&chanOscGrad.bgColor)) { updateChanOscGradTex=true; - logI("a point inserted"); } + ImGui::Combo("X Axis##AxisX",&chanOscColorX,chanOscRefs,GUI_OSCREF_MAX); + ImGui::Combo("Y Axis##AxisY",&chanOscColorY,chanOscRefs,GUI_OSCREF_MAX); - ImVec2 oldCurPos=ImGui::GetCursorPos(); - for (Gradient2DPoint& i: chanOscGrad.points) { - ImGui::SetCursorPos(ImVec2(gradLeft.x+i.x*gradSize.x,gradLeft.y+i.y*gradSize.y)); - ImGui::Text("Here"); - } - ImGui::SetCursorPos(oldCurPos); + ImGui::EndTable(); } + } else { + ImGui::SetNextItemWidth(400.0f*dpiScale); + ImGui::ColorPicker4("Color",(float*)&chanOscColor); + } - ImGui::TableNextColumn(); - if (ImGui::ColorEdit4("Background",(float*)&chanOscGrad.bgColor)) { - updateChanOscGradTex=true; - } - - ImGui::EndTable(); + if (ImGui::Button("OK")) { + chanOscOptions=false; } } @@ -129,12 +255,10 @@ void FurnaceGUI::drawChanOsc() { std::vector oscFFTs; std::vector oscChans; int chans=e->getTotalChannelCount(); - ImDrawList* dl=ImGui::GetWindowDrawList(); ImGuiWindow* window=ImGui::GetCurrentWindow(); ImVec2 waveform[512]; ImGuiStyle& style=ImGui::GetStyle(); - ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_OSC_WAVE]); for (int i=0; igetOscBuffer(i); @@ -196,93 +320,63 @@ void FurnaceGUI::drawChanOsc() { float maxLevel=-1.0f; float dcOff=0.0f; unsigned short needlePos=buf->needle; - if (chanOscWaveCorr) { - /* - double fftDataRate=(FURNACE_FFT_SIZE*FURNACE_FFT_RATE)/((double)buf->rate); - while (buf->readNeedle!=needlePos) { - fft->inBufPosFrac+=fftDataRate; - while (fft->inBufPosFrac>=1.0) { - chanOscLP0[ch]+=FURNACE_FFT_CUTOFF*((float)buf->data[buf->readNeedle]-chanOscLP0[ch]); - chanOscLP1[ch]+=FURNACE_FFT_CUTOFF*(chanOscLP0[ch]-chanOscLP1[ch]); - fft->inBuf[fft->inBufPos]=(double)chanOscLP1[ch]/32768.0; - if (++fft->inBufPos>=FURNACE_FFT_SIZE) { - fftw_execute(fft->plan); - fft->inBufPos=0; - fft->needle=buf->readNeedle; - } - fft->inBufPosFrac-=1.0; - } - buf->readNeedle++; - }*/ - - for (int i=0; iinBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0; - } - fftw_execute(fft->plan); - - // find origin frequency - int point=1; - double candAmp=0.0; - for (unsigned short i=1; i<512; i++) { - fftw_complex& f=fft->outBuf[i]; - // AMPLITUDE - double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8); - if (amp>candAmp) { - point=i; - candAmp=amp; - } - } - - // PHASE - fftw_complex& candPoint=fft->outBuf[point]; - double phase=((double)(displaySize*2)/(double)point)*(0.5+(atan2(candPoint[1],candPoint[0])/(M_PI*2))); - - //needlePos=fft->needle; - needlePos-=phase; - - /* - int alignment=0; - for (unsigned short i=0; idata[(unsigned short)(needlePos-i)])>fabs(buf->data[(unsigned short)(needlePos-alignment)])) { - alignment=i; - } - } - needlePos-=alignment; - */ - - //String cPhase=fmt::sprintf("%d cphase: %f",point,phase); - //dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); - - needlePos-=displaySize; - for (unsigned short i=0; i<512; i++) { - float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; - if (minLevel>y) minLevel=y; - if (maxLeveldata[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; - if (y<-0.5f) y=-0.5f; - if (y>0.5f) y=0.5f; - waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-(y-dcOff))); - } - } else { - needlePos-=displaySize; - for (unsigned short i=0; i<512; i++) { - float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; - if (minLevel>y) minLevel=y; - if (maxLeveldata[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; - if (y<-0.5f) y=-0.5f; - if (y>0.5f) y=0.5f; - waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-(y-dcOff))); + for (int i=0; iinBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0; + } + fftw_execute(fft->plan); + + // find origin frequency + int point=1; + double candAmp=0.0; + for (unsigned short i=1; i<512; i++) { + fftw_complex& f=fft->outBuf[i]; + // AMPLITUDE + double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8); + if (amp>candAmp) { + point=i; + candAmp=amp; } } + + // PHASE + fftw_complex& candPoint=fft->outBuf[point]; + double phase=((double)(displaySize*2)/(double)point)*(0.5+(atan2(candPoint[1],candPoint[0])/(M_PI*2))); + + if (chanOscWaveCorr) { + needlePos-=phase; + } + chanOscPitch[ch]=(float)point/32.0f; + + /* + String cPhase=fmt::sprintf("%d cphase: %f vol: %f",point,phase,chanOscVol[ch]); + dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); + */ + + needlePos-=displaySize; + for (unsigned short i=0; i<512; i++) { + float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; + if (minLevel>y) minLevel=y; + if (maxLeveldata[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; + if (y<-0.5f) y=-0.5f; + if (y>0.5f) y=0.5f; + waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-(y-dcOff))); + } + } + ImU32 color=ImGui::GetColorU32(chanOscColor); + if (chanOscUseGrad) { + float xVal=computeGradPos(chanOscColorX,ch); + float yVal=computeGradPos(chanOscColorY,ch); + + xVal=CLAMP(xVal,0.0f,1.0f); + yVal=CLAMP(yVal,0.0f,1.0f); + + color=chanOscGrad.get(xVal,1.0f-yVal); } dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale); } diff --git a/src/gui/gradient.cpp b/src/gui/gradient.cpp index c9b26fb80..510f2ad53 100644 --- a/src/gui/gradient.cpp +++ b/src/gui/gradient.cpp @@ -21,6 +21,16 @@ #include "imgui.h" #include +ImU32 Gradient2D::get(float x, float y) { + int xi=round(x*width); + int yi=round(y*height); + if (xi<0) xi=0; + if (xi>=(int)width) xi=width-1; + if (yi<0) yi=0; + if (yi>=(int)height) yi=height-1; + return grad[yi*width+xi]; +} + void Gradient2D::render() { ImU32* g=grad.get(); ImU32 bgColorU=ImGui::ColorConvertFloat4ToU32(bgColor); @@ -33,18 +43,18 @@ void Gradient2D::render() { // 2. insert points for (Gradient2DPoint& i: points) { float pDistSquared=i.distance*i.distance; - printf("shading this point %f %f\n",i.x,i.y); for (size_t j=0; j=pDistSquared) continue; - float dist=(1.0-(sqrt(distSquared)/i.distance))-i.spread; + float dist=(1.0-(sqrt(distSquared)/i.distance)); if (dist<0) dist=0; if (dist>1) dist=1; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index fbfc2b4d9..f3f246cd8 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4573,10 +4573,14 @@ FurnaceGUI::FurnaceGUI(): oscWindowSize(20.0f), oscZoomSlider(false), chanOscCols(3), + chanOscColorX(GUI_OSCREF_CENTER), + chanOscColorY(GUI_OSCREF_CENTER), chanOscWindowSize(20.0f), chanOscWaveCorr(true), chanOscOptions(false), updateChanOscGradTex(true), + chanOscUseGrad(false), + chanOscColor(1.0f,1.0f,1.0f,1.0f), chanOscGrad(64,64), chanOscGradTex(NULL), followLog(true), @@ -4655,6 +4659,9 @@ FurnaceGUI::FurnaceGUI(): memset(chanOscLP0,0,sizeof(float)*DIV_MAX_CHANS); memset(chanOscLP1,0,sizeof(float)*DIV_MAX_CHANS); + memset(chanOscVol,0,sizeof(float)*DIV_MAX_CHANS); + memset(chanOscPitch,0,sizeof(float)*DIV_MAX_CHANS); + memset(chanOscBright,0,sizeof(float)*DIV_MAX_CHANS); memset(lastCorrPos,0,sizeof(short)*DIV_MAX_CHANS); memset(acedData,0,23); diff --git a/src/gui/gui.h b/src/gui/gui.h index a538c5ba5..ddda9d53a 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -525,6 +525,21 @@ enum FurnaceGUIActions { GUI_ACTION_MAX }; +enum FurnaceGUIChanOscRef { + GUI_OSCREF_NONE=0, + GUI_OSCREF_CENTER, + GUI_OSCREF_FULL, + + GUI_OSCREF_FREQUENCY, + GUI_OSCREF_VOLUME, + GUI_OSCREF_CHANNEL, + GUI_OSCREF_BRIGHT, + + GUI_OSCREF_NOTE_TRIGGER, + + GUI_OSCREF_MAX +}; + enum PasteMode { GUI_PASTE_MODE_NORMAL=0, GUI_PASTE_MODE_MIX_FG, @@ -760,13 +775,15 @@ struct TouchPoint { struct Gradient2DPoint { ImVec4 color; - float x, y; + float x, y, prevX, prevY; float spread, distance; bool selected, grab; Gradient2DPoint(float xPos, float yPos): color(1,1,1,1), x(xPos), y(yPos), + prevX(0.0f), + prevY(0.0f), spread(0.0f), distance(0.5f), selected(false), @@ -1382,13 +1399,17 @@ class FurnaceGUI { bool oscZoomSlider; // per-channel oscilloscope - int chanOscCols; + int chanOscCols, chanOscColorX, chanOscColorY; float chanOscWindowSize; - bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex; + bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad; + ImVec4 chanOscColor; Gradient2D chanOscGrad; SDL_Texture* chanOscGradTex; float chanOscLP0[DIV_MAX_CHANS]; float chanOscLP1[DIV_MAX_CHANS]; + float chanOscVol[DIV_MAX_CHANS]; + float chanOscPitch[DIV_MAX_CHANS]; + float chanOscBright[DIV_MAX_CHANS]; unsigned short lastNeedlePos[DIV_MAX_CHANS]; unsigned short lastCorrPos[DIV_MAX_CHANS]; struct ChanOscStatus { @@ -1501,6 +1522,8 @@ class FurnaceGUI { bool importLayout(String path); bool exportLayout(String path); + float computeGradPos(int type, int chan); + void resetColors(); void resetKeybinds();