fix tuner, spectrum, vertical mixer layout

This commit is contained in:
Eknous-P 2025-10-31 23:19:43 +04:00
parent 8626937f89
commit 242af1d5f8
11 changed files with 648 additions and 208 deletions

View file

@ -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

View file

@ -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;
}

View file

@ -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),

View file

@ -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<int> 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();

View file

@ -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)),

View file

@ -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];

View file

@ -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<<IM_COL32_A_SHIFT);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0);
}
if (ImGui::VSliderFloat("##mixerMaster",ImVec2(40*dpiScale,maxY),&e->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<<IM_COL32_A_SHIFT);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0);
}
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::SliderFloat("##mixerMaster",&e->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<<IM_COL32_A_SHIFT);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0);
}
if (ImGui::VSliderFloat("##mixerMaster",ImVec2(40*dpiScale,maxY),&e->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; i<e->song.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<<IM_COL32_A_SHIFT);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0);
}
if (ImGui::VSliderFloat("##ChipVol",ImVec2(size.x-vTextWidth,volSliderHeight),&vol,0.0f,2.0f)) {
if (doInvert) {
if (vol<0.0001) vol=0.0001;
if (settings.mixerLayout) {
if (ImGui::BeginTable("mixerVectRow1", 2)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted(e->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<<IM_COL32_A_SHIFT);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0);
}
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::SliderFloat("##ChipVol",&vol,0.0f,2.0f)) {
if (doInvert) {
if (vol<0.0001) vol=0.0001;
}
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);
}
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<<IM_COL32_A_SHIFT);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,0);
}
if (ImGui::VSliderFloat("##ChipVol",ImVec2(size.x-vTextWidth,volSliderHeight),&vol,0.0f,2.0f)) {
if (doInvert) {
if (vol<0.0001) vol=0.0001;
}
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"));
}
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();

View file

@ -24,22 +24,6 @@
#include <fmt/printf.h>
#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) { \

View file

@ -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);

218
src/gui/spectrum.cpp Normal file
View file

@ -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 <IconsFontAwesome4.h>
#include <fftw3.h>
#include <math.h> // 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; j<spectrum.frequencies.size(); j++) {
i=spectrum.frequencies[j];
ImU32 color=0x22ffffff;
pos=spectrum.xZoom*size.x*(scaleFuncLog(i/(e->getAudioDescGot().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; j<maxRate; j*=10) {
for (int i=1; i<10; i++) {
freq = i*j;
if (freq>maxRate) 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; j<spectrum.bins; j++) {
int pos=(needle+j)&0x7fff;
double sample=0.0;
if (spectrum.mono) {
for (int i=0; i<chans; i++) {
sample+=e->oscBuf[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; i<count; i++) {
x=spectrum.xZoom*size.x*(scaleFuncLog((float)i/count)-spectrum.xOffset);
mag=2.0f*sqrt(spectrum.buffer[i][0]*spectrum.buffer[i][0]+spectrum.buffer[i][1]*spectrum.buffer[i][1])/count;
y=1.0-scaleFuncDb(mag);
spectrum.plot[i].x=origin.x+x;
spectrum.plot[i].y=origin.y+size.y*(y-spectrum.yOffset);
}
ImGui::PushClipRect(origin, origin+size, true);
dl->AddPolyline(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();
}

View file

@ -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; j<FURNACE_TUNER_FFT_SIZE; j++) {
int pos=(needle-FURNACE_TUNER_FFT_SIZE+j) & 0x7fff;
int pos=(needle-FURNACE_TUNER_FFT_SIZE+j)&0x7fff;
double sample=0.0;
for (int ch=0; ch<chans; ch++) {
sample+=e->oscBuf[ch][pos];
@ -59,95 +59,118 @@ void FurnaceGUI::drawTuner() {
fftw_execute(tunerPlan);
std::vector<double> 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; 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;
// harmonic product spectrum
int harmonics=1; // disabled for now...
for (int h=2; h<=harmonics; h++) {
for (int k=0; k<FURNACE_TUNER_FFT_SIZE/(2*h); k++) {
for (int k=8; k<FURNACE_TUNER_FFT_SIZE/(2*h); k++) {
mag[k]*=mag[k*h];
}
}
//peak with interpolation
int peakIndex = std::distance(mag.begin(), std::max_element(mag.begin(), mag.end()));
double sampleRate = e->getAudioDescGot().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; i<FURNACE_TUNER_FFT_SIZE; i++) {
plot[i].x=origin.x+size.x*((float)i/FURNACE_TUNER_FFT_SIZE);
plot[i].y=origin.y+size.y-size.y*(tunerFFTInBuf[i]+1.0f)/2.0f;
}
dl->AddPolyline(plot, FURNACE_TUNER_FFT_SIZE, 0xff00ff00, 0, 1.0f);
for (size_t i=0; i<mag.size(); i++) {
plot[i].x=origin.x+size.x*((float)i/mag.size());
plot[i].y=origin.y+size.y-size.y*mag[i]/FURNACE_TUNER_FFT_SIZE;
}
dl->AddPolyline(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; i<FURNACE_TUNER_FFT_SIZE; i++) {
// plot[i].x=origin.x+size.x*(float)i/FURNACE_TUNER_FFT_SIZE;
// plot[i].y=origin.y+size.y-size.y*(tunerFFTInBuf[i]+1.0f)/2.0f;
// }
// dl->AddPolyline(plot, FURNACE_TUNER_FFT_SIZE, 0xff00ff00, 0, 1.0f);
// for (size_t i=0; i<mag.size(); i++) {
// plot[i].x=origin.x+size.x*scaleFuncLog((float)i/mag.size());
// plot[i].y=origin.y+size.y-size.y*(mag[i]/FURNACE_TUNER_FFT_SIZE);
// }
// dl->AddPolyline(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();
}