From 242af1d5f8263a53c231b779f35b3c02ff360330 Mon Sep 17 00:00:00 2001 From: Eknous-P Date: Fri, 31 Oct 2025 23:19:43 +0400 Subject: [PATCH] fix tuner, spectrum, vertical mixer layout --- CMakeLists.txt | 1 + src/gui/doAction.cpp | 13 +- src/gui/gui.cpp | 32 ++++- src/gui/gui.h | 41 +++++- src/gui/guiConst.cpp | 21 +++ src/gui/guiConst.h | 4 + src/gui/mixer.cpp | 333 ++++++++++++++++++++++++++++--------------- src/gui/piano.cpp | 16 --- src/gui/settings.cpp | 22 ++- src/gui/spectrum.cpp | 218 ++++++++++++++++++++++++++++ src/gui/tuner.cpp | 155 +++++++++++--------- 11 files changed, 648 insertions(+), 208 deletions(-) create mode 100644 src/gui/spectrum.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d13d5e63..354a98dc9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1000,6 +1000,7 @@ src/gui/scaling.cpp src/gui/settings.cpp src/gui/songInfo.cpp src/gui/songNotes.cpp +src/gui/spectrum.cpp src/gui/speed.cpp src/gui/spoiler.cpp src/gui/stats.cpp diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 1f5e3ff85..2efc846d5 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -321,6 +321,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WINDOW_TUNER: nextWindow = GUI_WINDOW_TUNER; break; + case GUI_ACTION_WINDOW_SPECTRUM: + nextWindow = GUI_WINDOW_SPECTRUM; + break; case GUI_ACTION_WINDOW_CHANNELS: nextWindow=GUI_WINDOW_CHANNELS; break; @@ -429,9 +432,6 @@ void FurnaceGUI::doAction(int what) { case GUI_WINDOW_NOTES: notesOpen=false; break; - case GUI_WINDOW_TUNER: - notesOpen = false; - break; case GUI_WINDOW_CHANNELS: channelsOpen=false; break; @@ -470,6 +470,13 @@ void FurnaceGUI::doAction(int what) { break; case GUI_WINDOW_USER_PRESETS: userPresetsOpen=false; + break; + case GUI_WINDOW_TUNER: + tunerOpen=false; + break; + case GUI_WINDOW_SPECTRUM: + spectrumOpen=false; + break; default: break; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 52e2fa8b1..af5c60bf2 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3839,6 +3839,7 @@ bool FurnaceGUI::loop() { DECLARE_METRIC(piano) DECLARE_METRIC(notes) DECLARE_METRIC(tuner) + DECLARE_METRIC(spectrum) DECLARE_METRIC(channels) DECLARE_METRIC(patManager) DECLARE_METRIC(sysManager) @@ -4443,6 +4444,7 @@ bool FurnaceGUI::loop() { IMPORT_CLOSE(pianoOpen); IMPORT_CLOSE(notesOpen); IMPORT_CLOSE(tunerOpen); + IMPORT_CLOSE(spectrumOpen); IMPORT_CLOSE(channelsOpen); IMPORT_CLOSE(regViewOpen); IMPORT_CLOSE(logOpen); @@ -4809,6 +4811,7 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem(_("oscilloscope (X-Y)"),BIND_FOR(GUI_ACTION_WINDOW_XY_OSC),xyOscOpen)) xyOscOpen=!xyOscOpen; if (ImGui::MenuItem(_("volume meter"),BIND_FOR(GUI_ACTION_WINDOW_VOL_METER),volMeterOpen)) volMeterOpen=!volMeterOpen; if (ImGui::MenuItem(_("tuner"), BIND_FOR(GUI_ACTION_WINDOW_TUNER), tunerOpen)) tunerOpen = !tunerOpen; + if (ImGui::MenuItem(_("spectrum"), BIND_FOR(GUI_ACTION_WINDOW_SPECTRUM), spectrumOpen)) spectrumOpen = !spectrumOpen; ImGui::EndMenu(); } if (ImGui::BeginMenu(_("tempo"))) { @@ -5089,6 +5092,7 @@ bool FurnaceGUI::loop() { MEASURE(piano,drawPiano()); MEASURE(notes,drawNotes()); MEASURE(tuner,drawTuner()); + MEASURE(spectrum, drawSpectrum()); MEASURE(channels,drawChannels()); MEASURE(patManager,drawPatManager()); MEASURE(sysManager,drawSysManager()); @@ -8213,6 +8217,7 @@ void FurnaceGUI::syncState() { #endif notesOpen=e->getConfBool("notesOpen",false); tunerOpen=e->getConfBool("tunerOpen",false); + spectrumOpen=e->getConfBool("spectrumOpen", false); channelsOpen=e->getConfBool("channelsOpen",false); patManagerOpen=e->getConfBool("patManagerOpen",false); sysManagerOpen=e->getConfBool("sysManagerOpen",false); @@ -8273,6 +8278,12 @@ void FurnaceGUI::syncState() { oscZoomSlider=e->getConfBool("oscZoomSlider",false); oscWindowSize=e->getConfFloat("oscWindowSize",20.0f); + spectrum.bins=e->getConfInt("spectrumBins",2048); + spectrum.xZoom=e->getConfFloat("spectrumxZoom",1.0f); + spectrum.xOffset=e->getConfFloat("spectrumxOffset",0); + spectrum.yOffset=e->getConfFloat("spectrumyOffset",0); + spectrum.mono=e->getConfBool("spectrumMono",false); + pianoOctaves=e->getConfInt("pianoOctaves",pianoOctaves); pianoOctavesEdit=e->getConfInt("pianoOctavesEdit",pianoOctavesEdit); pianoOptions=e->getConfBool("pianoOptions",pianoOptions); @@ -8374,6 +8385,7 @@ void FurnaceGUI::commitState(DivConfig& conf) { conf.set("pianoOpen",pianoOpen); conf.set("notesOpen",notesOpen); conf.set("tunerOpen",tunerOpen); + conf.set("spectrumOpen",spectrumOpen); conf.set("channelsOpen",channelsOpen); conf.set("patManagerOpen",patManagerOpen); conf.set("sysManagerOpen",sysManagerOpen); @@ -8428,6 +8440,13 @@ void FurnaceGUI::commitState(DivConfig& conf) { conf.set("oscZoomSlider",oscZoomSlider); conf.set("oscWindowSize",oscWindowSize); + // commit spectrum state + conf.set("spectrumBins",spectrum.bins); + conf.set("spectrumxZoom",spectrum.xZoom); + conf.set("spectrumxOffset",spectrum.xOffset); + conf.set("spectrumyOffset",spectrum.yOffset); + conf.set("spectrumMono",spectrum.mono); + // commit piano state conf.set("pianoOctaves",pianoOctaves); conf.set("pianoOctavesEdit",pianoOctavesEdit); @@ -8544,14 +8563,17 @@ bool FurnaceGUI::finish(bool saveConfig) { fftw_free(tunerFFTOutBuf); tunerFFTOutBuf=NULL; } - if (spectrumPlan) { - fftw_free(spectrumPlan); - spectrumPlan=NULL; - } if (tunerPlan) { fftw_free(tunerPlan); tunerPlan=NULL; } + if (spectrum.in) { + delete[] spectrum.in; + spectrum.in=NULL; + } + if (spectrum.buffer) { + fftw_free(spectrum.buffer); + } return true; } @@ -9092,9 +9114,7 @@ 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 ef7987bb5..2b3839574 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -517,6 +517,10 @@ enum FurnaceGUIColors { GUI_COLOR_MEMORY_BANK6, GUI_COLOR_MEMORY_BANK7, + GUI_COLOR_TUNER_NEEDLE, + GUI_COLOR_TUNER_SCALE_LOW, + GUI_COLOR_TUNER_SCALE_HIGH, + GUI_COLOR_LOGLEVEL_ERROR, GUI_COLOR_LOGLEVEL_WARNING, GUI_COLOR_LOGLEVEL_INFO, @@ -552,6 +556,7 @@ enum FurnaceGUIWindows { GUI_WINDOW_PIANO, GUI_WINDOW_NOTES, GUI_WINDOW_TUNER, + GUI_WINDOW_SPECTRUM, GUI_WINDOW_CHANNELS, GUI_WINDOW_PAT_MANAGER, GUI_WINDOW_SYS_MANAGER, @@ -758,6 +763,7 @@ enum FurnaceGUIActions { GUI_ACTION_WINDOW_PIANO, GUI_ACTION_WINDOW_NOTES, GUI_ACTION_WINDOW_TUNER, + GUI_ACTION_WINDOW_SPECTRUM, GUI_ACTION_WINDOW_CHANNELS, GUI_ACTION_WINDOW_PAT_MANAGER, GUI_ACTION_WINDOW_SYS_MANAGER, @@ -2065,6 +2071,7 @@ class FurnaceGUI { int rackShowLEDs; int sampleImportInstDetune; int mixerStyle; + int mixerLayout; String mainFontPath; String headFontPath; String patFontPath; @@ -2319,6 +2326,7 @@ class FurnaceGUI { rackShowLEDs(1), sampleImportInstDetune(0), mixerStyle(1), + mixerLayout(0), mainFontPath(""), headFontPath(""), patFontPath(""), @@ -2392,7 +2400,7 @@ class FurnaceGUI { bool editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen; bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; - bool pianoOpen, notesOpen, tunerOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen; + bool pianoOpen, notesOpen, tunerOpen, spectrumOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen; bool subSongsOpen, findOpen, spoilerOpen, patManagerOpen, sysManagerOpen, clockOpen, speedOpen; bool groovesOpen, xyOscOpen, memoryOpen, csPlayerOpen, cvOpen, userPresetsOpen; @@ -2734,8 +2742,34 @@ class FurnaceGUI { // spectrum and tuner double* tunerFFTInBuf; fftw_complex* tunerFFTOutBuf; - fftw_plan spectrumPlan, tunerPlan; - int spectrumBins; + fftw_plan tunerPlan; + struct SpectrumSettings { + int bins; + float xZoom, xOffset; + float yOffset; + fftw_plan plan; + fftw_complex* buffer; + double* in; + ImVec2* plot; + std::vector frequencies; + bool update, running, mono; + bool showXGrid, showYGrid, showXScale, showYScale; + SpectrumSettings(): + bins(2048), + xZoom(1.0f), + xOffset(0.0f), + yOffset(0.0f), + buffer(NULL), + in(NULL), + frequencies({}), + update(true), + running(false), + mono(false), + showXGrid(true), + showYGrid(true), + showXScale(true), + showYScale(true) {} + } spectrum; // visualizer float keyHit[DIV_MAX_CHANS]; @@ -2980,6 +3014,7 @@ class FurnaceGUI { void drawPiano(); void drawNotes(bool asChild=false); void drawTuner(); + void drawSpectrum(); void drawChannels(); void drawPatManager(); void drawSysManager(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 60012af5a..8de7769a0 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -28,6 +28,22 @@ const int opOrder[4]={ 0, 2, 1, 3 }; +const float topKeyStarts[5]={ + 0.9f/7.0f, 2.1f/7.0f, 3.9f/7.0f, 5.0f/7.0f, 6.1f/7.0f +}; + +const int topKeyNotes[5]={ + 1, 3, 6, 8, 10 +}; + +const int bottomKeyNotes[7]={ + 0, 2, 4, 5, 7, 9, 11 +}; + +const bool isTopKey[12]={ + false, true, false, true, false, false, true, false, true, false, true, false +}; + const char* noteNames[180]={ "c_5", "c+5", "d_5", "d+5", "e_5", "f_5", "f+5", "g_5", "g+5", "a_5", "a+5", "b_5", "c_4", "c+4", "d_4", "d+4", "e_4", "f_4", "f+4", "g_4", "g+4", "a_4", "a+4", "b_4", @@ -642,6 +658,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WINDOW_PIANO", _N("Piano"), 0), D("WINDOW_NOTES", _N("Song Comments"), 0), D("WINDOW_TUNER", _N("Tuner"), 0), + D("WINDOW_SPECTRUM", _N("Spectrum"), 0), D("WINDOW_CHANNELS", _N("Channels"), 0), D("WINDOW_PAT_MANAGER", _N("Pattern Manager"), 0), D("WINDOW_SYS_MANAGER", _N("Chip Manager"), 0), @@ -1214,6 +1231,10 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_MEMORY_BANK6,"",ImVec4(0.5f,0.1f,1.0f,1.0f)), D(GUI_COLOR_MEMORY_BANK7,"",ImVec4(1.0f,0.1f,1.0f,1.0f)), + D(GUI_COLOR_TUNER_NEEDLE,"",ImVec4(0.1f,0.5f,0.9f,1.0f)), + D(GUI_COLOR_TUNER_SCALE_LOW,"",ImVec4(0.1f,1.0f,0.2f,1.0f)), + D(GUI_COLOR_TUNER_SCALE_HIGH,"",ImVec4(0.9f,0.1f,0.1f,1.0f)), + D(GUI_COLOR_LOGLEVEL_ERROR,"",ImVec4(1.0f,0.2f,0.2f,1.0f)), D(GUI_COLOR_LOGLEVEL_WARNING,"",ImVec4(1.0f,1.0f,0.2f,1.0f)), D(GUI_COLOR_LOGLEVEL_INFO,"",ImVec4(0.4f,1.0f,0.4f,1.0f)), diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 00beacd26..2b6618b8f 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -66,6 +66,10 @@ struct FurnaceGUIColorDef { }; extern const int opOrder[4]; +extern const float topKeyStarts[5]; +extern const int topKeyNotes[5]; +extern const int bottomKeyNotes[7]; +extern const bool isTopKey[12]; extern const char* noteNames[180]; extern const char* noteNamesG[180]; extern const char* noteNamesF[180]; diff --git a/src/gui/mixer.cpp b/src/gui/mixer.cpp index 4cb561723..3bb33a567 100644 --- a/src/gui/mixer.cpp +++ b/src/gui/mixer.cpp @@ -226,55 +226,84 @@ void FurnaceGUI::drawMixer() { if (ImGui::Begin("Mixer",&mixerOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking),_("Mixer"))) { if (ImGui::BeginTabBar("MixerView")) { if (ImGui::BeginTabItem(_("Mixer"))) { - float maxY=ImGui::GetContentRegionAvail().y; - VerticalText(maxY,true,_("Master Volume")); - ImGui::SameLine(); - if (settings.mixerStyle==2) { - ImVec2 pos=ImGui::GetCursorScreenPos(); - drawVolMeterInternal(ImGui::GetWindowDrawList(),ImRect(pos,pos+ImVec2(40*dpiScale,maxY)),peak,e->getAudioDescGot().outChans,false); - ImGui::PushStyleColor(ImGuiCol_FrameBg,0); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive,0); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered,127<song.masterVol,0,3,"%.2fx")) { - if (e->song.masterVol<0) e->song.masterVol=0; - if (e->song.masterVol>3) e->song.masterVol=3; - MARK_MODIFIED; - } rightClickable - ImGui::SameLine(); - if (settings.mixerStyle==2) { - ImGui::PopStyleColor(3); - ImGui::PopStyleVar(); - } else if (settings.mixerStyle==1) { - ImVec2 pos=ImGui::GetCursorScreenPos(); - drawVolMeterInternal(ImGui::GetWindowDrawList(),ImRect(pos,pos+ImVec2(40*dpiScale,maxY)),peak,e->getAudioDescGot().outChans,false); - ImGui::Dummy(ImVec2(40*dpiScale,maxY)); - ImGui::SameLine(); - } - const float itemWidth=60*dpiScale; - // figure out if we need to cut the height for the scrollbar - float itemCalcWidth= - itemWidth+ - 1.5f*ImGui::GetStyle().FramePadding.x+ - 4*ImGui::GetStyle().FramePadding.x+ - dpiScale+ // separator - (settings.mixerStyle==1?(itemWidth-ImGui::GetFontSize()-ImGui::GetStyle().FramePadding.x):0) - ; - float realwidth=ImGui::GetWindowWidth()-ImGui::GetCursorPosX(); - if ((itemCalcWidth*e->song.systemLen)>realwidth) maxY-=ImGui::GetStyle().ScrollbarSize; + float maxY=ImGui::GetContentRegionAvail().y; + if (settings.mixerLayout) { + ImGui::TextUnformatted(_("Master Volume")); + if (settings.mixerStyle==2) { + ImVec2 pos=ImGui::GetCursorScreenPos(), + size=ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetFontSize()+ImGui::GetStyle().FramePadding.y*2.0f); + drawVolMeterInternal(ImGui::GetWindowDrawList(),ImRect(pos,pos+size),peak,e->getAudioDescGot().outChans,true); + ImGui::PushStyleColor(ImGuiCol_FrameBg,0); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive,0); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered,127<song.masterVol,0,3,"%.2fx")) { + if (e->song.masterVol<0) e->song.masterVol=0; + if (e->song.masterVol>3) e->song.masterVol=3; + MARK_MODIFIED; + } rightClickable + if (settings.mixerStyle==2) { + ImGui::PopStyleColor(3); + ImGui::PopStyleVar(); + } else if (settings.mixerStyle==1) { + ImVec2 pos=ImGui::GetCursorScreenPos(), + size=ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetFontSize()+ImGui::GetStyle().FramePadding.y*2.0f); + drawVolMeterInternal(ImGui::GetWindowDrawList(),ImRect(pos,pos+size),peak,e->getAudioDescGot().outChans,true); + ImGui::Dummy(size); + } + } else { + VerticalText(maxY,true,_("Master Volume")); + ImGui::SameLine(); + if (settings.mixerStyle==2) { + ImVec2 pos=ImGui::GetCursorScreenPos(); + drawVolMeterInternal(ImGui::GetWindowDrawList(),ImRect(pos,pos+ImVec2(40*dpiScale,maxY)),peak,e->getAudioDescGot().outChans,false); + ImGui::PushStyleColor(ImGuiCol_FrameBg,0); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive,0); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered,127<song.masterVol,0,3,"%.2fx")) { + if (e->song.masterVol<0) e->song.masterVol=0; + if (e->song.masterVol>3) e->song.masterVol=3; + MARK_MODIFIED; + } rightClickable + ImGui::SameLine(); + if (settings.mixerStyle==2) { + ImGui::PopStyleColor(3); + ImGui::PopStyleVar(); + } else if (settings.mixerStyle==1) { + ImVec2 pos=ImGui::GetCursorScreenPos(); + drawVolMeterInternal(ImGui::GetWindowDrawList(),ImRect(pos,pos+ImVec2(40*dpiScale,maxY)),peak,e->getAudioDescGot().outChans,false); + ImGui::Dummy(ImVec2(40*dpiScale,maxY)); + ImGui::SameLine(); + } + // figure out if we need to cut the height for the scrollbar + float itemCalcWidth= + itemWidth+ + 1.5f*ImGui::GetStyle().FramePadding.x+ + 4*ImGui::GetStyle().FramePadding.x+ + dpiScale+ // separator + (settings.mixerStyle==1?(itemWidth-ImGui::GetFontSize()-ImGui::GetStyle().FramePadding.x):0) + ; + float realwidth=ImGui::GetWindowWidth()-ImGui::GetCursorPosX(); + if ((itemCalcWidth*e->song.systemLen)>realwidth) maxY-=ImGui::GetStyle().ScrollbarSize; + } if (ImGui::BeginChild("##mixerPerChipContainer",ImVec2(0,0),0,ImGuiWindowFlags_HorizontalScrollbar)) { for (int i=0; isong.systemLen; i++) { - ImGui::GetWindowDrawList()->AddRectFilled( - ImGui::GetCursorScreenPos(), - ImGui::GetCursorScreenPos()+ImVec2(dpiScale,maxY), - ImGui::GetColorU32(ImGuiCol_Separator) - ); - ImGui::Dummy(ImVec2(dpiScale,maxY)); - ImGui::SameLine(); + if (settings.mixerLayout==0) { + ImGui::GetWindowDrawList()->AddRectFilled( + ImGui::GetCursorScreenPos(), + ImGui::GetCursorScreenPos()+ImVec2(dpiScale,maxY), + ImGui::GetColorU32(ImGuiCol_Separator) + ); + ImGui::Dummy(ImVec2(dpiScale,maxY)); + ImGui::SameLine(); + } if (chipMixer(i,ImVec2(itemWidth,maxY))) MARK_MODIFIED; - ImGui::SameLine(); + if (settings.mixerLayout==0) ImGui::SameLine(); } } ImGui::EndChild(); @@ -452,76 +481,154 @@ bool FurnaceGUI::chipMixer(int which, ImVec2 size) { float vol=fabs(e->song.systemVol[which]); bool doInvert=e->song.systemVol[which]<0; - if (ImGui::Checkbox("##ChipInvert",&doInvert)) { - e->song.systemVol[which]=doInvert?-vol:vol; - ret=true; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip(_("Invert")); - } - // hack to get the same line from here - ImGui::SameLine(); - ImVec2 curPos=ImGui::GetCursorPos(); - ImGui::NewLine(); - - float volSliderHeight=size.y-ImGui::GetStyle().FramePadding.y*7-textHeight*2; - - VerticalText(volSliderHeight-(ImGui::GetCursorPosY()-curPos.y),true,"%s",e->getSystemName(e->song.system[which])); - - ImGui::SameLine(); - - float vTextWidth=textHeight+2*ImGui::GetStyle().FramePadding.x; - - ImGui::SetCursorPos(curPos); - ImVec2 pos=ImGui::GetCursorScreenPos(); - if (settings.mixerStyle==2) { - drawVolMeterInternal(ImGui::GetWindowDrawList(),ImRect(pos,pos+ImVec2(size.x-vTextWidth,volSliderHeight)),e->chipPeak[which],e->getDispatch(which)->getOutputCount(),false); - - ImGui::PushStyleColor(ImGuiCol_FrameBg,0); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive,0); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered,127<getSystemName(e->song.system[which])); + ImGui::TableNextColumn(); + if (ImGui::Checkbox("##ChipInvert",&doInvert)) { + e->song.systemVol[which]=doInvert?-vol:vol; + ret=true; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(_("Invert")); + } + ImGui::EndTable(); } - if (vol<0) vol=0; - if (vol>10) vol=10; - e->song.systemVol[which]=doInvert?-vol:vol; - ret=true; - } rightClickable - if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { - ImGui::SetTooltip(_("Volume")); - } - if (settings.mixerStyle==2) { - ImGui::PopStyleVar(1); - ImGui::PopStyleColor(3); - } else if (settings.mixerStyle==1) { - ImGui::SetCursorPos(curPos+ImVec2(size.x-vTextWidth+ImGui::GetStyle().FramePadding.x,0)); - pos=ImGui::GetCursorScreenPos(); - ImGui::Dummy(ImVec2(size.x-vTextWidth,volSliderHeight)); - drawVolMeterInternal(ImGui::GetWindowDrawList(),ImRect(pos,pos+ImVec2(size.x-vTextWidth,volSliderHeight)),e->chipPeak[which],e->getDispatch(which)->getOutputCount(),false); - } - float panSliderWidth=size.x+1.5f*ImGui::GetStyle().FramePadding.x+((settings.mixerStyle!=1)?0:size.x-vTextWidth+ImGui::GetStyle().FramePadding.x); - ImGui::SetNextItemWidth(panSliderWidth); - if (ImGui::SliderFloat("##ChipPan",&e->song.systemPan[which],-1.0f,1.0f)) { - if (e->song.systemPan[which]<-1.0f) e->song.systemPan[which]=-1.0f; - if (e->song.systemPan[which]>1.0f) e->song.systemPan[which]=1.0f; - ret=true; - } rightClickable - if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { - ImGui::SetTooltip(_("Panning")); - } + if (settings.mixerStyle==2) { + ImVec2 pos=ImGui::GetCursorScreenPos(), + posMax=pos+ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetFontSize()+ImGui::GetStyle().FramePadding.y*2.0f); + drawVolMeterInternal(ImGui::GetWindowDrawList(),ImRect(pos,posMax),e->chipPeak[which],e->getDispatch(which)->getOutputCount(),true); - ImGui::SetNextItemWidth(panSliderWidth); - if (ImGui::SliderFloat("##ChipPanFR",&e->song.systemPanFR[which],-1.0f,1.0f)) { - if (e->song.systemPanFR[which]<-1.0f) e->song.systemPanFR[which]=-1.0f; - if (e->song.systemPanFR[which]>1.0f) e->song.systemPanFR[which]=1.0f; - ret=true; - } rightClickable - if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { - ImGui::SetTooltip(_("Front/Rear")); + ImGui::PushStyleColor(ImGuiCol_FrameBg,0); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive,0); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered,127<10) vol=10; + e->song.systemVol[which]=doInvert?-vol:vol; + ret=true; + } rightClickable + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { + ImGui::SetTooltip(_("Volume")); + } + if (settings.mixerStyle==2) { + ImGui::PopStyleVar(1); + ImGui::PopStyleColor(3); + } + if (ImGui::BeginTable("mixerVectRow3", 2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::SliderFloat("##ChipPan",&e->song.systemPan[which],-1.0f,1.0f)) { + if (e->song.systemPan[which]<-1.0f) e->song.systemPan[which]=-1.0f; + if (e->song.systemPan[which]>1.0f) e->song.systemPan[which]=1.0f; + ret=true; + } rightClickable + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { + ImGui::SetTooltip(_("Panning")); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::SliderFloat("##ChipPanFR",&e->song.systemPanFR[which],-1.0f,1.0f)) { + if (e->song.systemPanFR[which]<-1.0f) e->song.systemPanFR[which]=-1.0f; + if (e->song.systemPanFR[which]>1.0f) e->song.systemPanFR[which]=1.0f; + ret=true; + } rightClickable + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { + ImGui::SetTooltip(_("Front/Rear")); + } + ImGui::EndTable(); + } + if (settings.mixerStyle==1) { + ImVec2 pos=ImGui::GetCursorScreenPos(), + size=ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetFontSize()+ImGui::GetStyle().FramePadding.y*2.0f); + ImGui::Dummy(size); + drawVolMeterInternal(ImGui::GetWindowDrawList(),ImRect(pos,pos+size),e->chipPeak[which],e->getDispatch(which)->getOutputCount(),true); + } + } else { + if (ImGui::Checkbox("##ChipInvert",&doInvert)) { + e->song.systemVol[which]=doInvert?-vol:vol; + ret=true; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(_("Invert")); + } + // hack to get the same line from here + ImGui::SameLine(); + ImVec2 curPos=ImGui::GetCursorPos(); + ImGui::NewLine(); + + float volSliderHeight=size.y-ImGui::GetStyle().FramePadding.y*7-textHeight*2; + + VerticalText(volSliderHeight-(ImGui::GetCursorPosY()-curPos.y),true,"%s",e->getSystemName(e->song.system[which])); + + ImGui::SameLine(); + + float vTextWidth=textHeight+2*ImGui::GetStyle().FramePadding.x; + + ImGui::SetCursorPos(curPos); + ImVec2 pos=ImGui::GetCursorScreenPos(); + if (settings.mixerStyle==2) { + drawVolMeterInternal(ImGui::GetWindowDrawList(),ImRect(pos,pos+ImVec2(size.x-vTextWidth,volSliderHeight)),e->chipPeak[which],e->getDispatch(which)->getOutputCount(),false); + + ImGui::PushStyleColor(ImGuiCol_FrameBg,0); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive,0); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered,127<10) vol=10; + e->song.systemVol[which]=doInvert?-vol:vol; + ret=true; + } rightClickable + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { + ImGui::SetTooltip(_("Volume")); + } + if (settings.mixerStyle==2) { + ImGui::PopStyleVar(1); + ImGui::PopStyleColor(3); + } else if (settings.mixerStyle==1) { + ImGui::SetCursorPos(curPos+ImVec2(size.x-vTextWidth+ImGui::GetStyle().FramePadding.x,0)); + pos=ImGui::GetCursorScreenPos(); + ImGui::Dummy(ImVec2(size.x-vTextWidth,volSliderHeight)); + drawVolMeterInternal(ImGui::GetWindowDrawList(),ImRect(pos,pos+ImVec2(size.x-vTextWidth,volSliderHeight)),e->chipPeak[which],e->getDispatch(which)->getOutputCount(),false); + } + float panSliderWidth=size.x+1.5f*ImGui::GetStyle().FramePadding.x+((settings.mixerStyle!=1)?0:size.x-vTextWidth+ImGui::GetStyle().FramePadding.x); + ImGui::SetNextItemWidth(panSliderWidth); + if (ImGui::SliderFloat("##ChipPan",&e->song.systemPan[which],-1.0f,1.0f)) { + if (e->song.systemPan[which]<-1.0f) e->song.systemPan[which]=-1.0f; + if (e->song.systemPan[which]>1.0f) e->song.systemPan[which]=1.0f; + ret=true; + } rightClickable + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { + ImGui::SetTooltip(_("Panning")); + } + + ImGui::SetNextItemWidth(panSliderWidth); + if (ImGui::SliderFloat("##ChipPanFR",&e->song.systemPanFR[which],-1.0f,1.0f)) { + if (e->song.systemPanFR[which]<-1.0f) e->song.systemPanFR[which]=-1.0f; + if (e->song.systemPanFR[which]>1.0f) e->song.systemPanFR[which]=1.0f; + ret=true; + } rightClickable + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { + ImGui::SetTooltip(_("Front/Rear")); + } } ImGui::EndGroup(); diff --git a/src/gui/piano.cpp b/src/gui/piano.cpp index 186a1b718..0efd98c14 100644 --- a/src/gui/piano.cpp +++ b/src/gui/piano.cpp @@ -24,22 +24,6 @@ #include #include "IconsFontAwesome4.h" -const float topKeyStarts[5]={ - 0.9f/7.0f, 2.1f/7.0f, 3.9f/7.0f, 5.0f/7.0f, 6.1f/7.0f -}; - -const int topKeyNotes[5]={ - 1, 3, 6, 8, 10 -}; - -const int bottomKeyNotes[7]={ - 0, 2, 4, 5, 7, 9, 11 -}; - -const bool isTopKey[12]={ - false, true, false, true, false, false, true, false, true, false, true, false -}; - #define VALUE_DIGIT(x,label) \ if (ImGui::Button(label,buttonSize)) { \ if (curWindow==GUI_WINDOW_ORDERS && orderEditMode>0) { \ diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 9971d5f08..fa388399e 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -3871,13 +3871,24 @@ void FurnaceGUI::drawSettings() { // SUBSECTION MIXER CONFIG_SUBSECTION(_("Mixer")) + ImGui::Text(_("Mixer layout:")); + ImGui::Indent(); + if (ImGui::RadioButton(_("Horizontal##mixl0"),settings.mixerLayout==0)) { + settings.mixerLayout=0; + settingsChanged=true; + } + if (ImGui::RadioButton(_("Vertical##mixl1"),settings.mixerLayout==1)) { + settings.mixerLayout=1; + settingsChanged=true; + } + ImGui::Unindent(); ImGui::Text(_("Mixer style:")); ImGui::Indent(); if (ImGui::RadioButton(_("No volume meters"),settings.mixerStyle==0)) { settings.mixerStyle=0; settingsChanged=true; } - if (ImGui::RadioButton(_("Volume meters to the side"),settings.mixerStyle==1)) { + if (ImGui::RadioButton(_("Volume meters separate"),settings.mixerStyle==1)) { settings.mixerStyle=1; settingsChanged=true; } @@ -4394,6 +4405,12 @@ void FurnaceGUI::drawSettings() { ImGui::TreePop(); } + if (ImGui::TreeNode(_("Tuner"))) { + UI_COLOR_CONFIG(GUI_COLOR_TUNER_NEEDLE,_("Needle##tuner")); + UI_COLOR_CONFIG(GUI_COLOR_TUNER_SCALE_LOW,_("Scale center")); + UI_COLOR_CONFIG(GUI_COLOR_TUNER_SCALE_HIGH,_("Scale edges")); + ImGui::TreePop(); + } if (ImGui::TreeNode(_("Log Viewer"))) { UI_COLOR_CONFIG(GUI_COLOR_LOGLEVEL_ERROR,_("Log level: Error")); UI_COLOR_CONFIG(GUI_COLOR_LOGLEVEL_WARNING,_("Log level: Warning")); @@ -5075,6 +5092,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { settings.rackShowLEDs=conf.getInt("rackShowLEDs",1); settings.mixerStyle=conf.getInt("mixerStyle",1); + settings.mixerLayout=conf.getInt("mixerLayout",1); settings.channelColors=conf.getInt("channelColors",1); settings.channelTextColors=conf.getInt("channelTextColors",0); @@ -5414,6 +5432,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { clampSetting(settings.songNotesWrap,0,1); clampSetting(settings.rackShowLEDs,0,1); clampSetting(settings.mixerStyle,0,2); + clampSetting(settings.mixerLayout,0,1); clampSetting(settings.cursorWheelStep,0,2); clampSetting(settings.vsync,0,4); clampSetting(settings.frameRateLimit,0,1000); @@ -5663,6 +5682,7 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("rackShowLEDs",settings.rackShowLEDs); conf.set("mixerStyle",settings.mixerStyle); + conf.set("mixerLayout",settings.mixerLayout); conf.set("channelColors",settings.channelColors); conf.set("channelTextColors",settings.channelTextColors); diff --git a/src/gui/spectrum.cpp b/src/gui/spectrum.cpp new file mode 100644 index 000000000..2c79f3ca7 --- /dev/null +++ b/src/gui/spectrum.cpp @@ -0,0 +1,218 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2025 tildearrow and contributors + * + * This program 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 of the License, or + * (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +// bulk of the code is from https://github.com/Eknous-P/unscope/blob/spectrum/src/gui/wins/spectrum.cpp + +#include "gui.h" +#include "imgui_internal.h" +#include +#include +#include // fmod + +inline float scaleFuncLog(float x) { + constexpr float base=100; + return log((base-1)*x+1.0f)/log(base); +} + +inline float scaleFuncDb(float y) { + return log10(y)*20.0f/70.0f+1; +} + +void FurnaceGUI::drawSpectrum() { + if (nextWindow==GUI_WINDOW_SPECTRUM) { + spectrumOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!spectrumOpen) return; + if (ImGui::Begin("Spectrum",&spectrumOpen,globalWinFlags,_("Spectrum"))) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0.0f,0.0f)); + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImVec2 origin=ImGui::GetWindowPos(), size=ImGui::GetWindowSize(); + float titleBar=ImGui::GetCurrentWindow()->TitleBarHeight; + origin.y+=titleBar; + size.y-=titleBar; + // x scale labels (precalc) + if (spectrum.showXScale) size.y-=ImGui::GetFontSize(); + // v scale labels + if (spectrum.showYScale) { + float padding=ImGui::GetStyle().FramePadding.x; + ImVec2 p1, textSize; + char buf[16]; + textSize=ImGui::CalcTextSize("-100"); + textSize.x+=5.f; + int lineOffset=spectrum.yOffset*8; + for (int z=lineOffset; z<=7+lineOffset; z++) { + p1.y=origin.y+size.y*((z+1)/8.f-spectrum.yOffset)-textSize.y/2.0f; + p1.x=origin.x+padding; + snprintf(buf,16,"-%2d",(z+1)*10); + dl->AddText(p1,ImGui::GetColorU32(ImGuiCol_Text),buf); + } + origin.x+=textSize.x+padding; + size.x-=textSize.x+padding; + } + // y grid + if (spectrum.showYGrid) { + int lines=((size.y>450)?16:8); + float offset=fmod(spectrum.yOffset*lines,1.0f)/lines; + for (unsigned char z=1; z<=lines; z++) { + dl->AddLine( + origin+ImVec2(0,size.y*((float)z/lines-offset)), + origin+ImVec2(size.x,size.y*((float)z/lines-offset)), + 0x55ffffff); + } + } + // x grid + if (spectrum.showXGrid || spectrum.showXScale) { + char buf[16]; + float pos=0, prevPos=0, i; + for (size_t j=0; jgetAudioDescGot().rate/2.0f))-spectrum.xOffset); + if (pos>size.x) break; + if (j%9==0) color=0x55ffffff; + if (spectrum.showXScale) { + if (i>=1000) { + snprintf(buf,16,"%dk",(int)i/1000); + } else { + snprintf(buf,16,"%d",(int)i); + } + float x=ImGui::CalcTextSize(buf).x; + if (pos-prevPos>x || j%9==0) dl->AddText( + origin+ImVec2(pos-x/2,size.y), + ImGui::GetColorU32(ImGuiCol_Text), + buf + ); + } + if (spectrum.showXGrid) dl->AddLine( + origin+ImVec2(pos,0), + origin+ImVec2(pos,size.y), + color); + prevPos=pos; + } + } + int chans=e->getAudioDescGot().outChans; + if (spectrum.update) { + spectrum.update=false; + spectrum.running=true; + if (spectrum.buffer) { + fftw_free(spectrum.buffer); + spectrum.buffer=NULL; + } + if (spectrum.in) { + delete[] spectrum.in; + spectrum.in=NULL; + } + if (spectrum.plot) { + delete[] spectrum.plot; + spectrum.plot=NULL; + } + spectrum.buffer=(fftw_complex*)fftw_malloc(sizeof(fftw_complex)*spectrum.bins); + if (!spectrum.buffer) spectrum.running=false; + spectrum.in=new double[spectrum.bins]; + if (!spectrum.in) spectrum.running=false; + spectrum.plot=new ImVec2[spectrum.bins/2]; + if (!spectrum.plot) spectrum.running=false; + spectrum.plan=fftw_plan_dft_r2c_1d(spectrum.bins,spectrum.in,spectrum.buffer,FFTW_ESTIMATE); + spectrum.frequencies.clear(); + float freq; + float maxRate=e->getAudioDescGot().rate/2; + for (int j=10; jmaxRate) break; + spectrum.frequencies.push_back((int)freq); + } + if (freq>maxRate) break; + } + if (spectrum.running) logV("spectrum ready!"); + } + if (spectrum.running) { + for (int z=spectrum.mono?0:(chans-1); z>=0; z--) { + // get buffer + memset(spectrum.in, 0, sizeof(double)*spectrum.bins); + int needle=e->oscReadPos-spectrum.bins; + + for (int j=0; joscBuf[i][pos]; + } + sample/=chans; + } else { + sample+=e->oscBuf[z][pos]; + } + spectrum.in[j]=sample*(0.5*(1.0-cos(2.0*M_PI*j/(spectrum.bins-1)))); + } + fftw_execute(spectrum.plan); + unsigned int count=0; + float mag=0.0f, x=0.0f, y=0.0f; + count=spectrum.bins/2; + for (unsigned int i=0; iAddPolyline(spectrum.plot, count, ImGui::GetColorU32(uiColors[spectrum.mono?GUI_COLOR_OSC_WAVE:GUI_COLOR_OSC_WAVE_CH0+z]), 0, 1.0f); + dl->PathFillConcave(ImGui::GetColorU32(uiColors[spectrum.mono?GUI_COLOR_OSC_WAVE:GUI_COLOR_OSC_WAVE_CH0+z])); + ImGui::PopClipRect(); + } + } + ImGui::PopStyleVar(); + if (ImGui::IsWindowHovered()) { + ImGui::SetCursorPosX(ImGui::GetWindowSize().x-ImGui::GetStyle().ItemSpacing.x-ImGui::CalcTextSize(ICON_FA_BARS).x); + ImGui::TextUnformatted(ICON_FA_BARS "##spectrumSettings"); + } + if (ImGui::BeginPopupContextItem("spectrumSettingsPopup",ImGuiPopupFlags_MouseButtonLeft)) { + ImGui::Checkbox(_("Mono##spec"), &spectrum.mono); + if (ImGui::InputScalar("Bins",ImGuiDataType_U32,&spectrum.bins)) { + if (spectrum.bins<32) spectrum.bins=32; + if (spectrum.bins>32768) spectrum.bins=32768; + spectrum.update=true; + } + ImGui::Separator(); + if (ImGui::SliderFloat("X Zoom", &spectrum.xZoom, 1.0f, 10.0f)) { + if (spectrum.xZoom<1.0f) spectrum.xZoom=1.0f; + if (spectrum.xZoom>10.0f) spectrum.xZoom=10.0f; + } + if (ImGui::SliderFloat("X Offset", &spectrum.xOffset, 0.0f, 1.0f)) { + if (spectrum.xOffset<0.0f) spectrum.xOffset=0.0f; + if (spectrum.xOffset>1.0f) spectrum.xOffset=1.0f; + } + ImGui::Separator(); + if (ImGui::SliderFloat("Y Offset", &spectrum.yOffset, 0.0f, 1.0f)) { + if (spectrum.yOffset<0.0f) spectrum.yOffset=0.0f; + if (spectrum.yOffset>1.0f) spectrum.yOffset=1.0f; + } + ImGui::Separator(); + ImGui::Checkbox(_("Show X Grid"), &spectrum.showXGrid); + ImGui::Checkbox(_("Show Y Grid"), &spectrum.showYGrid); + ImGui::Checkbox(_("Show X Scale"), &spectrum.showXScale); + ImGui::Checkbox(_("Show Y Scale"), &spectrum.showYScale); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SPECTRUM; + ImGui::End(); +} diff --git a/src/gui/tuner.cpp b/src/gui/tuner.cpp index 284079336..3cc24e641 100644 --- a/src/gui/tuner.cpp +++ b/src/gui/tuner.cpp @@ -19,22 +19,22 @@ #define _USE_MATH_DEFINES #include "gui.h" +#include "guiConst.h" #include "imgui.h" #include "imgui_internal.h" #include "misc/cpp/imgui_stdlib.h" -#define FURNACE_TUNER_FFT_SIZE 8192 +#define FURNACE_TUNER_FFT_SIZE 16384 void FurnaceGUI::drawTuner() { - if (nextWindow == GUI_WINDOW_TUNER) { - tunerOpen = true; + if (nextWindow==GUI_WINDOW_TUNER) { + tunerOpen=true; ImGui::SetNextWindowFocus(); - nextWindow = GUI_WINDOW_NOTHING; + nextWindow=GUI_WINDOW_NOTHING; } if (!tunerOpen) return; - if (ImGui::Begin("Tuner", &tunerOpen, globalWinFlags, _("Tuner"))) { - - //fft buffer + if (ImGui::Begin("Tuner",&tunerOpen,globalWinFlags,_("Tuner"))) { + // fft buffer if (!tunerFFTInBuf) tunerFFTInBuf=new double[FURNACE_TUNER_FFT_SIZE]; if (!tunerFFTOutBuf) tunerFFTOutBuf=(fftw_complex*)fftw_malloc(sizeof(fftw_complex)*FURNACE_TUNER_FFT_SIZE); @@ -46,7 +46,7 @@ void FurnaceGUI::drawTuner() { int needle=e->oscReadPos; for (int j=0; joscBuf[ch][pos]; @@ -59,95 +59,118 @@ void FurnaceGUI::drawTuner() { fftw_execute(tunerPlan); std::vector mag(FURNACE_TUNER_FFT_SIZE/2); - for (int k=0; k < FURNACE_TUNER_FFT_SIZE / 2; k++) { + mag[0]=0;mag[1]=0;mag[2]=0;mag[3]=0; // skip some of the low frequencies + for (int k=4; kgetAudioDescGot().rate; + double sampleRate=e->getAudioDescGot().rate; + // peak with interpolation + int peakIndex=std::distance(mag.begin(),std::max_element(mag.begin(),mag.end())); - double freq = 0.0; - if (peakIndex > 0 && peakIndex < (int)mag.size() - 1) { - double alpha = mag[peakIndex - 1]; - double beta = mag[peakIndex]; - double gamma = mag[peakIndex + 1]; - double p = 0.5 * (alpha - gamma) / (alpha - 2.0 * beta + gamma); - freq = (peakIndex + p) * (sampleRate / (double)FURNACE_TUNER_FFT_SIZE); - } - else { - freq = (double)peakIndex * (sampleRate / (double)FURNACE_TUNER_FFT_SIZE); + double freq=0.0; + if (peakIndex>0 && peakIndex<(int)mag.size()-1) { + double alpha=mag[peakIndex-1]; + double beta=mag[peakIndex]; + double gamma=mag[peakIndex+1]; + double p=0.5*(alpha-gamma)/(alpha-2.0*beta+gamma); + freq=(peakIndex+p)*(sampleRate/(double)FURNACE_TUNER_FFT_SIZE); + } else { + freq=(double)peakIndex*(sampleRate/(double)FURNACE_TUNER_FFT_SIZE); } - - //tuning formulas - double noteExact=0; + // tuning formulas + double noteExact=0.0; int noteRounded=0; double cents=0.0f; - if (freq > 0 && freq < 5000.0) { - noteExact=CLAMP(log2(freq / e->song.tuning) * 12.0 + 117.0,0,180); + String noteText="---", subtext=""; + if (freq>0 && freq<5000.0) { + noteExact=CLAMP(log2(freq/e->song.tuning)*12.0+117.0,0,180); noteRounded=round(noteExact); - cents=noteExact-noteRounded; + cents=(noteExact-noteRounded); + noteText=fmt::sprintf("%s",noteName(noteRounded)); + subtext=fmt::sprintf("%3.3f Hz %dc", freq, (int)(cents*100.0f)); } - 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; + 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) - ); + // 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); + // dl->AddLine(origin+ImVec2(size.x*scaleFuncLog((double)peakIndex/(mag.size()-1)),0),origin+ImVec2(size.x*scaleFuncLog((double)peakIndex/(mag.size()-1)),size.y), 0xff000fff); + // delete[] plot; + const float boxHeight=20.0f*dpiScale; + const float needleHeight=18.0f*dpiScale; + ImU32 lowColor=ImGui::GetColorU32(uiColors[GUI_COLOR_TUNER_SCALE_LOW]); + ImU32 highColor=ImGui::GetColorU32(uiColors[GUI_COLOR_TUNER_SCALE_HIGH]); + + ImGui::Dummy(ImVec2(size.x,boxHeight)); + dl->AddRectFilledMultiColor(origin,origin+ImVec2(size.x/2.0f,boxHeight),highColor,lowColor,lowColor,highColor); + dl->AddRectFilledMultiColor(origin+ImVec2(size.x/2.0f,0),origin+ImVec2(size.x,boxHeight),lowColor,highColor,highColor,lowColor); + dl->AddLine(origin+ImVec2(size.x/2.0f,0),origin+ImVec2(size.x/2.0f,boxHeight),ImGui::GetColorU32(uiColors[GUI_COLOR_TUNER_NEEDLE]),2.0f*dpiScale); + + float needleX=size.x*(0.5f+cents); + dl->AddLine(origin+ImVec2(needleX, boxHeight-needleHeight),origin+ImVec2(needleX,needleHeight),ImGui::GetColorU32(uiColors[GUI_COLOR_TUNER_NEEDLE]),4.0f*dpiScale); // 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::SetCursorPosX((size.x-textSize.x)/2.0f); + ImGui::TextUnformatted(noteText.c_str()); ImGui::PopFont(); - // needle - needleCenter = needleCenter+ImVec2( - needleLength*sin(angle)*trim, - -needleLength*cos(angle)*trim - ); - - dl->AddLine(needleCenter, needleTip, 0xff00ffff, 2.0f); + textSize=ImGui::CalcTextSize(subtext.c_str()); + ImGui::SetCursorPosX((size.x-textSize.x)/2.0f); + ImGui::TextUnformatted(subtext.c_str()); + // piano + int noteMod=noteRounded%12; + ImVec2 pianoPos=ImGui::GetCursorScreenPos(); + pianoPos.x=ImGui::GetWindowPos().x; + ImVec2 pianoSize=ImVec2(ImGui::GetWindowSize().x,40.0f); + float keySize=pianoSize.x/7.0f; + for (int i=0; i<7; i++) { + dl->AddRectFilled( + pianoPos+ImVec2(keySize*i,0), + pianoPos+ImVec2(keySize*(i+1),pianoSize.y), + ImGui::GetColorU32(uiColors[bottomKeyNotes[i]==noteMod?GUI_COLOR_PIANO_KEY_BOTTOM_HIT:GUI_COLOR_PIANO_KEY_BOTTOM])); + dl->AddLine( + pianoPos+ImVec2(keySize*i,0), + pianoPos+ImVec2(keySize*i,pianoSize.y), + ImGui::GetColorU32(uiColors[GUI_COLOR_PIANO_BACKGROUND]), + dpiScale); + } + for (int i=0; i<5; i++) { + dl->AddRectFilled( + pianoPos+ImVec2(pianoSize.x*topKeyStarts[i]-keySize/3.0f,0), + pianoPos+ImVec2(pianoSize.x*topKeyStarts[i]+keySize/3.0f,2.0f*pianoSize.y/3.0f), + ImGui::GetColorU32(uiColors[topKeyNotes[i]==noteMod?GUI_COLOR_PIANO_KEY_TOP_HIT:GUI_COLOR_PIANO_KEY_TOP])); + } + ImGui::Dummy(pianoSize); } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) - curWindow = GUI_WINDOW_TUNER; + curWindow=GUI_WINDOW_TUNER; ImGui::End(); }