#define _USE_MATH_DEFINES // OK, sorry for inserting the define here but I'm so tired of this extension /** * Furnace Tracker - multi-system chiptune tracker * Copyright (C) 2021-2023 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. */ // I hate you clangd extension! // how about you DON'T insert random headers before this freaking important // define!!!!!! #include "gui.h" #include "util.h" #include "../ta-log.h" #include "../fileutils.h" #include "imgui.h" #include "imgui_internal.h" #include "ImGuiFileDialog.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" #include "plot_nolerp.h" #include "guiConst.h" #include "intConst.h" #include "scaling.h" #include "introTune.h" #include #include #include #include #ifdef _WIN32 #include #include #include #include "../utfutils.h" #define LAYOUT_INI "\\layout.ini" #define BACKUPS_DIR "\\backups" #else #include #include #include #include #include #define LAYOUT_INI "/layout.ini" #define BACKUPS_DIR "/backups" #endif #ifdef IS_MOBILE #define MOBILE_UI_DEFAULT true #else #define MOBILE_UI_DEFAULT false #endif #include "actionUtil.h" bool Particle::update(float frameTime) { pos.x+=speed.x*frameTime; pos.y+=speed.y*frameTime; speed.x*=1.0-((1.0-friction)*frameTime); speed.y*=1.0-((1.0-friction)*frameTime); speed.y+=gravity*frameTime; life-=lifeSpeed*frameTime; return (life>0); } void FurnaceGUI::centerNextWindow(const char* name, float w, float h) { if (ImGui::IsPopupOpen(name)) { if (settings.centerPopup) { ImGui::SetNextWindowPos(ImVec2(w*0.5,h*0.5),ImGuiCond_Always,ImVec2(0.5,0.5)); } } } void FurnaceGUI::bindEngine(DivEngine* eng) { e=eng; wavePreview.setEngine(e); } const char* FurnaceGUI::noteName(short note, short octave) { if (note==100) { return noteOffLabel; } else if (note==101) { // note off and envelope release return noteRelLabel; } else if (note==102) { // envelope release only return macroRelLabel; } else if (octave==0 && note==0) { return emptyLabel; } else if (note==0 && octave!=0) { return "BUG"; } int seek=(note+(signed char)octave*12)+60; if (seek<0 || seek>=180) { return "???"; } if (settings.flatNotes) { if (settings.germanNotation) return noteNamesGF[seek]; return noteNamesF[seek]; } if (settings.germanNotation) return noteNamesG[seek]; return noteNames[seek]; } bool FurnaceGUI::decodeNote(const char* what, short& note, short& octave) { if (strlen(what)!=3) return false; if (strcmp(what,"...")==0) { note=0; octave=0; return true; } if (strcmp(what,"???")==0) { note=0; octave=0; return true; } if (strcmp(what,"OFF")==0) { note=100; octave=0; return true; } if (strcmp(what,"===")==0) { note=101; octave=0; return true; } if (strcmp(what,"REL")==0) { note=102; octave=0; return true; } for (int i=0; i<180; i++) { if (strcmp(what,noteNames[i])==0) { if ((i%12)==0) { note=12; octave=(unsigned char)((i/12)-6); } else { note=i%12; octave=(unsigned char)((i/12)-5); } return true; } } return false; } String FurnaceGUI::encodeKeyMap(std::map& map) { String ret; for (std::map::value_type& i: map) { ret+=fmt::sprintf("%d:%d;",i.first,i.second); } return ret; } void FurnaceGUI::decodeKeyMap(std::map& map, String source) { map.clear(); bool inValue=false; bool negateKey=false; bool negateValue=false; int key=0; int val=0; for (char& i: source) { switch (i) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (inValue) { val*=10; val+=i-'0'; } else { key*=10; key+=i-'0'; } break; case '-': if (inValue) { negateValue=true; } else { negateKey=true; } break; case ':': inValue=true; break; case ';': if (inValue) { map[negateKey?-key:key]=negateValue?-val:val; } key=0; val=0; inValue=false; negateKey=false; negateValue=false; break; } } } void FurnaceGUI::encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex, bool bit30) { target=""; char buf[32]; for (int i=0; imacroMax) macro[macroLen]=macroMax; macroLen++; buf=0; } break; } if (macroLen>=256) break; } if (hasVal && macroLen<256) { hasVal=false; macro[macroLen]=negaBuf?-buf:buf; negaBuf=false; if (macro[macroLen]macroMax) macro[macroLen]=macroMax; macroLen++; buf=0; } } void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLen, unsigned char& macroLoop, int macroMin, int macroMax, unsigned char& macroRel, bool bit30) { int buf=0; bool negaBuf=false; bool setBit30=false; bool hasVal=false; macroLen=0; macroLoop=255; macroRel=255; for (char& i: source) { switch (i) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': hasVal=true; buf*=10; buf+=i-'0'; break; case '-': if (!hasVal) { hasVal=true; negaBuf=true; } break; case '@': if (bit30) { setBit30=true; } break; case ' ': if (hasVal) { hasVal=false; macro[macroLen]=negaBuf?-buf:buf; negaBuf=false; if (macro[macroLen]macroMax) macro[macroLen]=macroMax; if (setBit30) macro[macroLen]^=0x40000000; setBit30=false; macroLen++; buf=0; } break; case '|': if (hasVal) { hasVal=false; macro[macroLen]=negaBuf?-buf:buf; negaBuf=false; if (macro[macroLen]macroMax) macro[macroLen]=macroMax; if (setBit30) macro[macroLen]^=0x40000000; setBit30=false; macroLen++; buf=0; } if (macroLoop==255) { macroLoop=macroLen; } break; case '/': if (hasVal) { hasVal=false; macro[macroLen]=negaBuf?-buf:buf; negaBuf=false; if (macro[macroLen]macroMax) macro[macroLen]=macroMax; if (setBit30) macro[macroLen]^=0x40000000; setBit30=false; macroLen++; buf=0; } if (macroRel==255) { macroRel=macroLen; } break; } if (macroLen>=255) break; } if (hasVal && macroLen<255) { hasVal=false; macro[macroLen]=negaBuf?-buf:buf; negaBuf=false; if (macro[macroLen]macroMax) macro[macroLen]=macroMax; if (setBit30) macro[macroLen]^=0x40000000; setBit30=false; macroLen++; buf=0; } } #define CW_ADDITION(T) \ if (p_min!=NULL && p_max!=NULL) { \ if (*((T*)p_min)>*((T*)p_max)) { \ if (wheelY<0) { \ if ((*((T*)p_data)-wheelY)>*((T*)p_min)) { \ *((T*)p_data)=*((T*)p_min); \ } else { \ *((T*)p_data)-=wheelY; \ } \ } else { \ if ((*((T*)p_data)-wheelY)<*((T*)p_max)) { \ *((T*)p_data)=*((T*)p_max); \ } else { \ *((T*)p_data)-=wheelY; \ } \ } \ } else { \ if (wheelY>0) { \ if ((*((T*)p_data)+wheelY)>*((T*)p_max)) { \ *((T*)p_data)=*((T*)p_max); \ } else { \ *((T*)p_data)+=wheelY; \ } \ } else { \ if ((*((T*)p_data)+wheelY)<*((T*)p_min)) { \ *((T*)p_data)=*((T*)p_min); \ } else { \ *((T*)p_data)+=wheelY; \ } \ } \ } \ } bool FurnaceGUI::CWSliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { flags^=ImGuiSliderFlags_AlwaysClamp; if (ImGui::SliderScalar(label,data_type,p_data,p_min,p_max,format,flags)) { return true; } if (ImGui::IsItemHovered() && ctrlWheeling) { switch (data_type) { case ImGuiDataType_U8: CW_ADDITION(unsigned char); break; case ImGuiDataType_S8: CW_ADDITION(signed char); break; case ImGuiDataType_U16: CW_ADDITION(unsigned short); break; case ImGuiDataType_S16: CW_ADDITION(short); break; case ImGuiDataType_U32: CW_ADDITION(unsigned int); break; case ImGuiDataType_S32: CW_ADDITION(int); break; case ImGuiDataType_Float: CW_ADDITION(float); break; case ImGuiDataType_Double: CW_ADDITION(double); break; } return true; } return false; } bool FurnaceGUI::CWVSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { flags^=ImGuiSliderFlags_AlwaysClamp; if (ImGui::VSliderScalar(label,size,data_type,p_data,p_min,p_max,format,flags)) { return true; } if (ImGui::IsItemHovered() && ctrlWheeling) { switch (data_type) { case ImGuiDataType_U8: CW_ADDITION(unsigned char); break; case ImGuiDataType_S8: CW_ADDITION(signed char); break; case ImGuiDataType_U16: CW_ADDITION(unsigned short); break; case ImGuiDataType_S16: CW_ADDITION(short); break; case ImGuiDataType_U32: CW_ADDITION(unsigned int); break; case ImGuiDataType_S32: CW_ADDITION(int); break; case ImGuiDataType_Float: CW_ADDITION(float); break; case ImGuiDataType_Double: CW_ADDITION(double); break; } return true; } return false; } bool FurnaceGUI::CWSliderInt(const char* label, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return CWSliderScalar(label,ImGuiDataType_S32,v,&v_min,&v_max,format,flags); } bool FurnaceGUI::CWSliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { return CWSliderScalar(label,ImGuiDataType_Float,v,&v_min,&v_max,format,flags); } bool FurnaceGUI::CWVSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { return CWVSliderScalar(label,size,ImGuiDataType_S32,v,&v_min,&v_max,format,flags); } bool FurnaceGUI::InvCheckbox(const char* label, bool* value) { bool t=!(*value); if (ImGui::Checkbox(label,&t)) { *value=!t; return true; } return false; } void FurnaceGUI::sameLineMaybe(float width) { if (width<0.0f) width=ImGui::GetFrameHeight(); ImGui::SameLine(); if (ImGui::GetContentRegionAvail().xgetSystemChips(which); } */ return e->getSystemName(which); } void FurnaceGUI::updateScroll(int amount) { float lineHeight=(patFont->FontSize+2*dpiScale); nextScroll=lineHeight*amount; haveHitBounds=false; } void FurnaceGUI::addScroll(int amount) { float lineHeight=(patFont->FontSize+2*dpiScale); nextAddScroll=lineHeight*amount; haveHitBounds=false; } void FurnaceGUI::setFileName(String name) { #ifdef _WIN32 wchar_t ret[4096]; WString ws=utf8To16(name.c_str()); int index=0; for (wchar_t& i: ws) { ret[index++]=i; if (index>=4095) break; } ret[index]=0; backupLock.lock(); if (GetFullPathNameW(ws.c_str(),4095,ret,NULL)==0) { curFileName=name; } else { curFileName=utf16To8(ret); } backupLock.unlock(); #else char ret[4096]; backupLock.lock(); if (realpath(name.c_str(),ret)==NULL) { curFileName=name; } else { curFileName=ret; } backupLock.unlock(); #endif updateWindowTitle(); pushRecentFile(curFileName); if (settings.alwaysPlayIntro==2) shortIntro=true; } void FurnaceGUI::updateWindowTitle() { String title; switch (settings.titleBarInfo) { case 0: title="Furnace"; break; case 1: if (e->song.name.empty()) { title="Furnace"; } else { title=fmt::sprintf("%s - Furnace",e->song.name); } break; case 2: if (curFileName.empty()) { title="Furnace"; } else { String shortName; size_t pos=curFileName.rfind(DIR_SEPARATOR); if (pos==String::npos) { shortName=curFileName; } else { shortName=curFileName.substr(pos+1); } title=fmt::sprintf("%s - Furnace",shortName); } break; case 3: if (curFileName.empty()) { title="Furnace"; } else { title=fmt::sprintf("%s - Furnace",curFileName); } break; } if (settings.titleBarSys) { if (e->song.systemName!="") { title+=fmt::sprintf(" (%s)",e->song.systemName); } } if (sdlWin!=NULL) SDL_SetWindowTitle(sdlWin,title.c_str()); } void FurnaceGUI::autoDetectSystem() { std::map sysCountMap; std::map sysConfMap; for (int i=0; isong.systemLen; i++) { auto it=sysCountMap.find(e->song.system[i]); if (it==sysCountMap.cend()) { sysCountMap[e->song.system[i]]=1; } else { it->second++; } sysConfMap[e->song.system[i]]=e->song.systemFlags[i]; } logV("sysCountMap:"); for (std::pair k: sysCountMap) { logV("%s: %d",e->getSystemName(k.first),k.second); } bool isMatch=false; std::map defCountMap; std::map defConfMap; for (FurnaceGUISysCategory& i: sysCategories) { for (FurnaceGUISysDef& j: i.systems) { defCountMap.clear(); defConfMap.clear(); for (FurnaceGUISysDefChip& k: j.orig) { auto it=defCountMap.find(k.sys); if (it==defCountMap.cend()) { defCountMap[k.sys]=1; } else { it->second++; } DivConfig dc; dc.loadFromMemory(k.flags); defConfMap[k.sys]=dc; } if (defCountMap.size()!=sysCountMap.size()) continue; isMatch=true; /*logV("trying on defCountMap: %s",j.name); for (std::pair k: defCountMap) { logV("- %s: %d",e->getSystemName(k.first),k.second); }*/ for (std::pair k: defCountMap) { auto countI=sysCountMap.find(k.first); if (countI==sysCountMap.cend()) { isMatch=false; break; } else if (countI->second!=k.second) { isMatch=false; break; } auto confI=sysConfMap.find(k.first); if (confI==sysConfMap.cend()) { isMatch=false; break; } DivConfig& sysDC=confI->second; auto defConfI=defConfMap.find(k.first); if (defConfI==defConfMap.cend()) { isMatch=false; break; } for (std::pair l: defConfI->second.configMap()) { if (!sysDC.has(l.first)) { isMatch=false; break; } if (sysDC.getString(l.first,"")!=l.second) { isMatch=false; break; } } if (!isMatch) break; } if (isMatch) { logV("match found!"); e->song.systemName=j.name; break; } } if (isMatch) break; } if (!isMatch) { bool isFirst=true; e->song.systemName=""; for (std::pair k: sysCountMap) { if (!isFirst) e->song.systemName+=" + "; if (k.second>1) { e->song.systemName+=fmt::sprintf("%d×",k.second); } e->song.systemName+=e->getSystemName(k.first); isFirst=false; } } } ImVec4 FurnaceGUI::channelColor(int ch) { switch (settings.channelColors) { case 0: return uiColors[GUI_COLOR_CHANNEL_BG]; break; case 1: return uiColors[GUI_COLOR_CHANNEL_FM+e->getChannelType(ch)]; break; case 2: return uiColors[GUI_COLOR_INSTR_STD+e->getPreferInsType(ch)]; break; } // invalid return uiColors[GUI_COLOR_TEXT]; } ImVec4 FurnaceGUI::channelTextColor(int ch) { switch (settings.channelTextColors) { case 0: return uiColors[GUI_COLOR_CHANNEL_FG]; break; case 1: return uiColors[GUI_COLOR_CHANNEL_FM+e->getChannelType(ch)]; break; case 2: return uiColors[GUI_COLOR_INSTR_STD+e->getPreferInsType(ch)]; break; } // invalid return uiColors[GUI_COLOR_TEXT]; } const char* defaultLayout="[Window][DockSpaceViewport_11111111]\n\ Pos=0,24\n\ Size=1280,776\n\ Collapsed=0\n\ \n\ [Window][Debug##Default]\n\ Pos=54,43\n\ Size=400,400\n\ Collapsed=0\n\ \n\ [Window][Play/Edit Controls]\n\ Pos=181,208\n\ Size=45,409\n\ Collapsed=0\n\ \n\ [Window][Song Information]\n\ Pos=978,24\n\ Size=302,179\n\ Collapsed=0\n\ DockId=0x0000000F,0\n\ \n\ [Window][Orders]\n\ Pos=0,24\n\ Size=345,217\n\ Collapsed=0\n\ DockId=0x00000007,0\n\ \n\ [Window][Instruments]\n\ Pos=653,24\n\ Size=323,217\n\ Collapsed=0\n\ DockId=0x00000006,0\n\ \n\ [Window][Wavetables]\n\ Pos=653,24\n\ Size=323,217\n\ Collapsed=0\n\ DockId=0x00000006,1\n\ \n\ [Window][Samples]\n\ Pos=653,24\n\ Size=323,217\n\ Collapsed=0\n\ DockId=0x00000006,2\n\ \n\ [Window][Pattern]\n\ Pos=0,243\n\ Size=939,557\n\ Collapsed=0\n\ DockId=0x00000017,0\n\ \n\ [Window][Instrument Editor]\n\ Pos=229,126\n\ Size=856,652\n\ Collapsed=0\n\ \n\ [Window][Warning]\n\ Pos=481,338\n\ Size=264,86\n\ Collapsed=0\n\ \n\ [Window][Sample Editor]\n\ Pos=47,216\n\ Size=1075,525\n\ Collapsed=0\n\ \n\ [Window][About Furnace]\n\ Size=1280,755\n\ Collapsed=0\n\ \n\ [Window][Save File##FileDialog]\n\ Pos=340,177\n\ Size=600,400\n\ Collapsed=0\n\ \n\ [Window][Wavetable Editor]\n\ Pos=253,295\n\ Size=748,378\n\ Collapsed=0\n\ \n\ [Window][Settings]\n\ Pos=655,224\n\ Size=1280,941\n\ Collapsed=0\n\ \n\ [Window][Error]\n\ Pos=491,342\n\ Size=514,71\n\ Collapsed=0\n\ \n\ [Window][Mixer]\n\ Pos=429,198\n\ Size=453,355\n\ Collapsed=0\n\ \n\ [Window][Oscilloscope]\n\ Pos=347,94\n\ Size=304,105\n\ Collapsed=0\n\ DockId=0x0000000E,0\n\ \n\ [Window][Volume Meter]\n\ Pos=1248,243\n\ Size=32,557\n\ Collapsed=0\n\ DockId=0x0000000C,0\n\ \n\ [Window][Debug]\n\ Pos=113,148\n\ Size=945,473\n\ Collapsed=0\n\ \n\ [Window][Load Sample##FileDialog]\n\ Pos=40,0\n\ Size=1200,755\n\ Collapsed=0\n\ \n\ [Window][Open File##FileDialog]\n\ Pos=250,143\n\ Size=779,469\n\ Collapsed=0\n\ \n\ [Window][Export Audio##FileDialog]\n\ Pos=339,177\n\ Size=601,400\n\ Collapsed=0\n\ \n\ [Window][Rendering...]\n\ Pos=585,342\n\ Size=114,71\n\ Collapsed=0\n\ \n\ [Window][Export VGM##FileDialog]\n\ Pos=340,177\n\ Size=600,400\n\ Collapsed=0\n\ \n\ [Window][Warning##Save FileFileDialogOverWriteDialog]\n\ Pos=390,351\n\ Size=500,71\n\ Collapsed=0\n\ \n\ [Window][Statistics]\n\ Pos=0,581\n\ Size=1246,219\n\ Collapsed=0\n\ DockId=0x00000016,0\n\ \n\ [Window][Warning##Export VGMFileDialogOverWriteDialog]\n\ Pos=390,351\n\ Size=500,71\n\ Collapsed=0\n\ \n\ [Window][Compatibility Flags]\n\ Pos=388,132\n\ Size=580,641\n\ Collapsed=0\n\ \n\ [Window][Song Comments]\n\ Pos=60,60\n\ Size=395,171\n\ Collapsed=0\n\ \n\ [Window][Warning##Export AudioFileDialogOverWriteDialog]\n\ Pos=381,351\n\ Size=500,71\n\ Collapsed=0\n\ \n\ [Window][Select Font##FileDialog]\n\ Pos=340,177\n\ Size=600,400\n\ Collapsed=0\n\ \n\ [Window][Channels]\n\ Pos=60,60\n\ Size=368,449\n\ Collapsed=0\n\ \n\ [Window][Register View]\n\ Pos=829,243\n\ Size=417,557\n\ Collapsed=0\n\ DockId=0x00000014,0\n\ \n\ [Window][New Song]\n\ Pos=267,110\n\ Size=746,534\n\ Collapsed=0\n\ \n\ [Window][Edit Controls]\n\ Pos=347,24\n\ Size=304,68\n\ Collapsed=0\n\ DockId=0x0000000D,0\n\ \n\ [Window][Play Controls]\n\ Pos=347,201\n\ Size=304,40\n\ Collapsed=0\n\ DockId=0x0000000A,0\n\ \n\ [Window][Subsongs]\n\ Pos=978,24\n\ Size=302,217\n\ Collapsed=0\n\ DockId=0x00000010,1\n\ \n\ [Window][Oscilloscope (per-channel)]\n\ Pos=1095,243\n\ Size=151,557\n\ Collapsed=0\n\ DockId=0x00000012,0\n\ \n\ [Window][Piano]\n\ Pos=177,669\n\ Size=922,118\n\ Collapsed=0\n\ \n\ [Window][Log Viewer]\n\ Pos=60,60\n\ Size=541,637\n\ Collapsed=0\n\ \n\ [Window][Pattern Manager]\n\ Pos=60,60\n\ Size=1099,366\n\ Collapsed=0\n\ \n\ [Window][Chip Manager]\n\ Pos=60,60\n\ Size=490,407\n\ Collapsed=0\n\ \n\ [Window][Speed]\n\ Pos=978,24\n\ Size=302,217\n\ Collapsed=0\n\ DockId=0x00000010,2\n\ \n\ [Window][Song Info##Song Information]\n\ Pos=978,24\n\ Size=302,217\n\ Collapsed=0\n\ DockId=0x00000010,0\n\ \n\ [Window][Effect List]\n\ Pos=941,243\n\ Size=305,557\n\ Collapsed=0\n\ DockId=0x00000018,0\n\ \n\ [Window][Intro]\n\ Pos=0,0\n\ Size=2560,1600\n\ Collapsed=0\n\ \n\ [Window][Welcome]\n\ Pos=944,666\n\ Size=672,268\n\ Collapsed=0\n\ \n\ [Window][Grooves]\n\ Pos=416,314\n\ Size=463,250\n\ Collapsed=0\n\ \n\ [Window][Clock]\n\ Pos=60,60\n\ Size=145,184\n\ Collapsed=0\n\ \n\ [Docking][Data]\n\ DockSpace ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,24 Size=1280,776 Split=Y Selected=0x6C01C512\n\ DockNode ID=0x00000001 Parent=0x8B93E3BD SizeRef=1280,217 Split=X Selected=0xF3094A52\n\ DockNode ID=0x00000003 Parent=0x00000001 SizeRef=976,231 Split=X Selected=0x65CC51DC\n\ DockNode ID=0x00000007 Parent=0x00000003 SizeRef=345,231 HiddenTabBar=1 Selected=0x8F5BFC9A\n\ DockNode ID=0x00000008 Parent=0x00000003 SizeRef=629,231 Split=X Selected=0xD2AD486B\n\ DockNode ID=0x00000005 Parent=0x00000008 SizeRef=304,406 Split=Y Selected=0x6D682373\n\ DockNode ID=0x00000009 Parent=0x00000005 SizeRef=292,175 Split=Y Selected=0x6D682373\n\ DockNode ID=0x0000000D Parent=0x00000009 SizeRef=292,68 HiddenTabBar=1 Selected=0xE57B1A9D\n\ DockNode ID=0x0000000E Parent=0x00000009 SizeRef=292,105 HiddenTabBar=1 Selected=0x6D682373\n\ DockNode ID=0x0000000A Parent=0x00000005 SizeRef=292,40 HiddenTabBar=1 Selected=0x0DE44CFF\n\ DockNode ID=0x00000006 Parent=0x00000008 SizeRef=323,406 Selected=0xB75D68C7\n\ DockNode ID=0x00000004 Parent=0x00000001 SizeRef=302,231 Split=Y Selected=0x60B9D088\n\ DockNode ID=0x0000000F Parent=0x00000004 SizeRef=302,179 Selected=0x60B9D088\n\ DockNode ID=0x00000010 Parent=0x00000004 SizeRef=302,36 Selected=0x82BEE2E5\n\ DockNode ID=0x00000002 Parent=0x8B93E3BD SizeRef=1280,512 Split=X Selected=0x6C01C512\n\ DockNode ID=0x0000000B Parent=0x00000002 SizeRef=1246,503 Split=X Selected=0xB9ADD0D5\n\ DockNode ID=0x00000011 Parent=0x0000000B SizeRef=1093,557 Split=X Selected=0xB9ADD0D5\n\ DockNode ID=0x00000013 Parent=0x00000011 SizeRef=827,557 Split=Y Selected=0xB9ADD0D5\n\ DockNode ID=0x00000015 Parent=0x00000013 SizeRef=1246,336 Split=X Selected=0xB9ADD0D5\n\ DockNode ID=0x00000017 Parent=0x00000015 SizeRef=939,557 CentralNode=1 HiddenTabBar=1 Selected=0xB9ADD0D5\n\ DockNode ID=0x00000018 Parent=0x00000015 SizeRef=305,557 Selected=0xB94874DD\n\ DockNode ID=0x00000016 Parent=0x00000013 SizeRef=1246,219 Selected=0xAD8E88F2\n\ DockNode ID=0x00000014 Parent=0x00000011 SizeRef=417,557 Selected=0x425428FB\n\ DockNode ID=0x00000012 Parent=0x0000000B SizeRef=151,557 HiddenTabBar=1 Selected=0x4C07BC58\n\ DockNode ID=0x0000000C Parent=0x00000002 SizeRef=32,503 HiddenTabBar=1 Selected=0x644DA2C1\n"; void FurnaceGUI::prepareLayout() { FILE* check; check=ps_fopen(finalLayoutPath,"r"); if (check!=NULL) { fclose(check); return; } // copy initial layout logI("loading default layout."); check=ps_fopen(finalLayoutPath,"w"); if (check==NULL) { logW("could not write default layout!"); return; } fwrite(defaultLayout,1,strlen(defaultLayout),check); fclose(check); } float FurnaceGUI::calcBPM(const DivGroovePattern& speeds, float hz, int vN, int vD) { float hl=e->curSubSong->hilightA; if (hl<=0.0f) hl=4.0f; float timeBase=e->curSubSong->timeBase+1; float speedSum=0; for (int i=0; iwalkSong(loopOrder,loopRow,loopEnd); memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS); if (!followPattern) e->setOrder(curOrder); if (row>0) { e->playToRow(row); } else { e->play(); } curNibble=false; orderNibble=false; activeNotes.clear(); } void FurnaceGUI::setOrder(unsigned char order, bool forced) { curOrder=order; if (followPattern || forced) { e->setOrder(order); } } void FurnaceGUI::stop() { bool wasPlaying=e->isPlaying(); e->walkSong(loopOrder,loopRow,loopEnd); e->stop(); curNibble=false; orderNibble=false; if (followPattern && wasPlaying) { nextScroll=-1.0f; nextAddScroll=0.0f; cursor.y=e->getRow(); if (selStart.xCoarse==selEnd.xCoarse && selStart.xFine==selEnd.xFine && selStart.y==selEnd.y && !selecting) { selStart=cursor; selEnd=cursor; } updateScroll(cursor.y); } } void FurnaceGUI::previewNote(int refChan, int note, bool autoNote) { e->setMidiBaseChan(refChan); e->synchronized([this,note]() { e->autoNoteOn(-1,curIns,note); }); } void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) { auto it=noteKeys.find(scancode); if (it!=noteKeys.cend()) { int key=it->second; int num=12*curOctave+key; if (num<-60) num=-60; // C-(-5) if (num>119) num=119; // B-9 if (key==100) return; if (key==101) return; if (key==102) return; e->synchronized([this,num]() { e->autoNoteOff(-1,num); }); } } void FurnaceGUI::noteInput(int num, int key, int vol) { DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true); bool removeIns=false; prepareUndo(GUI_UNDO_PATTERN_EDIT); if (key==GUI_NOTE_OFF) { // note off pat->data[cursor.y][0]=100; pat->data[cursor.y][1]=0; removeIns=true; } else if (key==GUI_NOTE_OFF_RELEASE) { // note off + env release pat->data[cursor.y][0]=101; pat->data[cursor.y][1]=0; removeIns=true; } else if (key==GUI_NOTE_RELEASE) { // env release only pat->data[cursor.y][0]=102; pat->data[cursor.y][1]=0; removeIns=true; } else { pat->data[cursor.y][0]=num%12; pat->data[cursor.y][1]=num/12; if (pat->data[cursor.y][0]==0) { pat->data[cursor.y][0]=12; pat->data[cursor.y][1]--; } pat->data[cursor.y][1]=(unsigned char)pat->data[cursor.y][1]; if (latchIns==-2) { if (curIns>=(int)e->song.ins.size()) curIns=-1; if (curIns>=0) { pat->data[cursor.y][2]=curIns; } } else if (latchIns!=-1 && !e->song.ins.empty()) { pat->data[cursor.y][2]=MIN(((int)e->song.ins.size())-1,latchIns); } int maxVol=e->getMaxVolumeChan(cursor.xCoarse); if (latchVol!=-1) { pat->data[cursor.y][3]=MIN(maxVol,latchVol); } else if (vol!=-1) { pat->data[cursor.y][3]=(vol*maxVol)/127; } if (latchEffect!=-1) pat->data[cursor.y][4]=latchEffect; if (latchEffectVal!=-1) pat->data[cursor.y][5]=latchEffectVal; } if (removeIns) { if (settings.removeInsOff) { pat->data[cursor.y][2]=-1; } if (settings.removeVolOff) { pat->data[cursor.y][3]=-1; } } makeUndo(GUI_UNDO_PATTERN_EDIT); editAdvance(); curNibble=false; } void FurnaceGUI::valueInput(int num, bool direct, int target) { DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true); prepareUndo(GUI_UNDO_PATTERN_EDIT); if (target==-1) target=cursor.xFine+1; if (direct) { pat->data[cursor.y][target]=num&0xff; } else { if (pat->data[cursor.y][target]==-1) pat->data[cursor.y][target]=0; if (!settings.pushNibble && !curNibble) { pat->data[cursor.y][target]=num; } else { pat->data[cursor.y][target]=((pat->data[cursor.y][target]<<4)|num)&0xff; } } if (cursor.xFine==1) { // instrument if (pat->data[cursor.y][target]>=(int)e->song.ins.size()) { pat->data[cursor.y][target]&=0x0f; if (pat->data[cursor.y][target]>=(int)e->song.ins.size()) { pat->data[cursor.y][target]=(int)e->song.ins.size()-1; } } if (settings.absorbInsInput) { curIns=pat->data[cursor.y][target]; wavePreviewInit=true; updateFMPreview=true; } makeUndo(GUI_UNDO_PATTERN_EDIT); if (direct) { curNibble=false; } else { if (e->song.ins.size()<16) { curNibble=false; editAdvance(); } else { curNibble=!curNibble; if (!curNibble) editAdvance(); } } } else if (cursor.xFine==2) { if (curNibble) { if (pat->data[cursor.y][target]>e->getMaxVolumeChan(cursor.xCoarse)) pat->data[cursor.y][target]=e->getMaxVolumeChan(cursor.xCoarse); } else { pat->data[cursor.y][target]&=15; } makeUndo(GUI_UNDO_PATTERN_EDIT); if (direct) { curNibble=false; } else { if (e->getMaxVolumeChan(cursor.xCoarse)<16) { curNibble=false; editAdvance(); } else { curNibble=!curNibble; if (!curNibble) editAdvance(); } } } else { makeUndo(GUI_UNDO_PATTERN_EDIT); if (direct) { curNibble=false; } else { curNibble=!curNibble; if (!curNibble) { if (!settings.effectCursorDir) { editAdvance(); } else { if (settings.effectCursorDir==2) { if (++cursor.xFine>=(3+(e->curPat[cursor.xCoarse].effectCols*2))) { cursor.xFine=3; } } else { if (cursor.xFine&1) { cursor.xFine++; } else { editAdvance(); cursor.xFine--; } } } } } } } #define changeLatch(x) \ if (x<0) x=0; \ if (!latchNibble && !settings.pushNibble) x=0; \ x=(x<<4)|num; \ latchNibble=!latchNibble; \ if (!latchNibble) { \ if (++latchTarget>4) latchTarget=0; \ } void FurnaceGUI::keyDown(SDL_Event& ev) { if (introPos<11.0 && !shortIntro) return; if (ImGuiFileDialog::Instance()->IsOpened()) return; if (aboutOpen) return; int mapped=ev.key.keysym.sym; if (ev.key.keysym.mod&KMOD_CTRL) { mapped|=FURKMOD_CTRL; } if (ev.key.keysym.mod&KMOD_ALT) { mapped|=FURKMOD_ALT; } if (ev.key.keysym.mod&KMOD_GUI) { mapped|=FURKMOD_META; } if (ev.key.keysym.mod&KMOD_SHIFT) { mapped|=FURKMOD_SHIFT; } if (bindSetActive) { if (!ev.key.repeat) { switch (ev.key.keysym.sym) { case SDLK_LCTRL: case SDLK_RCTRL: case SDLK_LALT: case SDLK_RALT: case SDLK_LGUI: case SDLK_RGUI: case SDLK_LSHIFT: case SDLK_RSHIFT: bindSetPending=false; actionKeys[bindSetTarget]=(mapped&(~FURK_MASK))|0xffffff; break; default: actionKeys[bindSetTarget]=mapped; bindSetActive=false; bindSetPending=false; bindSetTarget=0; bindSetPrevValue=0; parseKeybinds(); break; } } return; } if (latchTarget) { if (mapped==SDLK_DELETE || mapped==SDLK_BACKSPACE) { switch (latchTarget) { case 1: latchIns=-1; break; case 2: latchVol=-1; break; case 3: latchEffect=-1; break; case 4: latchEffectVal=-1; break; } } else { auto it=valueKeys.find(ev.key.keysym.sym); if (it!=valueKeys.cend()) { int num=it->second; switch (latchTarget) { case 1: // instrument changeLatch(latchIns); break; case 2: // volume changeLatch(latchVol); break; case 3: // effect changeLatch(latchEffect); break; case 4: // effect value changeLatch(latchEffectVal); break; } } } return; } if (sampleMapWaitingInput) { if (sampleMapColumn==1) { // TODO: map? if (ev.key.keysym.scancode==SDL_SCANCODE_DELETE) { alterSampleMap(true,-1); return; } auto it=noteKeys.find(ev.key.keysym.scancode); if (it!=noteKeys.cend()) { int key=it->second; int num=12*curOctave+key; if (num<-60) num=-60; // C-(-5) if (num>119) num=119; // B-9 alterSampleMap(true,num); return; } } else { // TODO: map? if (ev.key.keysym.scancode==SDL_SCANCODE_DELETE) { alterSampleMap(false,-1); return; } auto it=valueKeys.find(ev.key.keysym.sym); if (it!=valueKeys.cend()) { int num=it->second; if (num<10) { alterSampleMap(false,num); return; } } } } // PER-WINDOW KEYS switch (curWindow) { case GUI_WINDOW_PATTERN: { auto actionI=actionMapPat.find(mapped); if (actionI!=actionMapPat.cend()) { int action=actionI->second; if (action>0) { doAction(action); return; } } // pattern input otherwise if (mapped&(FURKMOD_ALT|FURKMOD_CTRL|FURKMOD_META|FURKMOD_SHIFT)) break; if (!ev.key.repeat) { if (cursor.xFine==0) { // note auto it=noteKeys.find(ev.key.keysym.scancode); if (it!=noteKeys.cend()) { int key=it->second; int num=12*curOctave+key; if (num<-60) num=-60; // C-(-5) if (num>119) num=119; // B-9 if (edit) { noteInput(num,key); } } } else if (edit) { // value auto it=valueKeys.find(ev.key.keysym.sym); if (it!=valueKeys.cend()) { int num=it->second; valueInput(num); } } } break; } case GUI_WINDOW_ORDERS: { auto actionI=actionMapOrders.find(mapped); if (actionI!=actionMapOrders.cend()) { int action=actionI->second; if (action>0) { doAction(action); return; } } // order input otherwise if (mapped&(FURKMOD_ALT|FURKMOD_CTRL|FURKMOD_META|FURKMOD_SHIFT)) break; if (orderEditMode!=0) { auto it=valueKeys.find(ev.key.keysym.sym); if (it!=valueKeys.cend()) { int num=it->second; if (orderCursor>=0 && orderCursorgetTotalChannelCount()) { prepareUndo(GUI_UNDO_CHANGE_ORDER); e->lockSave([this,num]() { if (!curNibble && !settings.pushNibble) e->curOrders->ord[orderCursor][curOrder]=0; e->curOrders->ord[orderCursor][curOrder]=((e->curOrders->ord[orderCursor][curOrder]<<4)|num); }); MARK_MODIFIED; curNibble=!curNibble; if (orderEditMode==2 || orderEditMode==3) { if (!curNibble) { if (orderEditMode==2) { orderCursor++; if (orderCursor>=e->getTotalChannelCount()) orderCursor=0; } else if (orderEditMode==3) { if (curOrdercurSubSong->ordersLen-1) { setOrder(curOrder+1); } } } } e->walkSong(loopOrder,loopRow,loopEnd); makeUndo(GUI_UNDO_CHANGE_ORDER); } } } break; } case GUI_WINDOW_SAMPLE_EDIT: { auto actionI=actionMapSample.find(mapped); if (actionI!=actionMapSample.cend()) { int action=actionI->second; if (action>0) { doAction(action); return; } } break; } case GUI_WINDOW_INS_LIST: { auto actionI=actionMapInsList.find(mapped); if (actionI!=actionMapInsList.cend()) { int action=actionI->second; if (action>0) { doAction(action); return; } } break; } case GUI_WINDOW_WAVE_LIST: { auto actionI=actionMapWaveList.find(mapped); if (actionI!=actionMapWaveList.cend()) { int action=actionI->second; if (action>0) { doAction(action); return; } } break; } case GUI_WINDOW_SAMPLE_LIST: { auto actionI=actionMapSampleList.find(mapped); if (actionI!=actionMapSampleList.cend()) { int action=actionI->second; if (action>0) { doAction(action); return; } } break; } default: break; } // GLOBAL KEYS auto actionI=actionMapGlobal.find(mapped); if (actionI!=actionMapGlobal.cend()) { int action=actionI->second; if (action>0) { doAction(action); return; } } } void FurnaceGUI::keyUp(SDL_Event& ev) { // nothing for now } bool dirExists(String s) { return dirExists(s.c_str()); } void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { bool hasOpened=false; switch (type) { case GUI_FILE_OPEN: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openLoad( "Open File", {"compatible files", "*.fur *.dmf *.mod *.fc13 *.fc14 *.smod *.fc", "all files", "*"}, workingDirSong, dpiScale ); break; case GUI_FILE_OPEN_BACKUP: if (!dirExists(backupPath)) { showError("no backups made yet!"); break; } hasOpened=fileDialog->openLoad( "Restore Backup", {"Furnace song", "*.fur"}, backupPath+String(DIR_SEPARATOR_STR), dpiScale ); break; case GUI_FILE_SAVE: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openSave( "Save File", {"Furnace song", "*.fur"}, workingDirSong, dpiScale ); break; case GUI_FILE_SAVE_DMF: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openSave( "Save File", {"DefleMask 1.1.3 module", "*.dmf"}, workingDirSong, dpiScale ); break; case GUI_FILE_SAVE_DMF_LEGACY: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openSave( "Save File", {"DefleMask 1.0/legacy module", "*.dmf"}, workingDirSong, dpiScale ); break; case GUI_FILE_INS_OPEN: case GUI_FILE_INS_OPEN_REPLACE: prevIns=-3; if (prevInsData!=NULL) { delete prevInsData; prevInsData=NULL; } prevInsData=new DivInstrument; *prevInsData=*e->getIns(curIns); if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); hasOpened=fileDialog->openLoad( "Load Instrument", {"all compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.opli *.opni *.y12 *.bnk *.ff *.gyb *.opm *.wopl *.wopn", "Furnace instrument", "*.fui", "DefleMask preset", "*.dmp", "TFM Music Maker instrument", "*.tfi", "VGM Music Maker instrument", "*.vgi", "Scream Tracker 3 instrument", "*.s3i", "SoundBlaster instrument", "*.sbi", "Wohlstand OPL instrument", "*.opli", "Wohlstand OPN instrument", "*.opni", "Gens KMod patch dump", "*.y12", "BNK file (AdLib)", "*.bnk", "FF preset bank", "*.ff", "2612edit GYB preset bank", "*.gyb", "VOPM preset bank", "*.opm", "Wohlstand WOPL bank", "*.wopl", "Wohlstand WOPN bank", "*.wopn", "all files", "*"}, workingDirIns, dpiScale, [this](const char* path) { int sampleCountBefore=e->song.sampleLen; std::vector instruments=e->instrumentFromFile(path,false); if (!instruments.empty()) { if (e->song.sampleLen!=sampleCountBefore) { e->renderSamplesP(); } if (curFileDialog==GUI_FILE_INS_OPEN_REPLACE) { if (prevIns==-3) { prevIns=curIns; } if (prevIns>=0 && prevIns<=(int)e->song.ins.size()) { *e->song.ins[prevIns]=*instruments[0]; } } else { e->loadTempIns(instruments[0]); if (curIns!=-2) { prevIns=curIns; } curIns=-2; } } for (DivInstrument* i: instruments) delete i; }, (type==GUI_FILE_INS_OPEN) ); break; case GUI_FILE_INS_SAVE: if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); hasOpened=fileDialog->openSave( "Save Instrument", {"Furnace instrument", "*.fui"}, workingDirIns, dpiScale ); break; case GUI_FILE_INS_SAVE_DMP: if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); hasOpened=fileDialog->openSave( "Save Instrument", {"DefleMask preset", "*.dmp"}, workingDirIns, dpiScale ); break; case GUI_FILE_WAVE_OPEN: case GUI_FILE_WAVE_OPEN_REPLACE: if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); hasOpened=fileDialog->openLoad( "Load Wavetable", {"compatible files", "*.fuw *.dmw", "all files", "*"}, workingDirWave, dpiScale, NULL, // TODO (type==GUI_FILE_WAVE_OPEN) ); break; case GUI_FILE_WAVE_SAVE: if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); hasOpened=fileDialog->openSave( "Save Wavetable", {"Furnace wavetable", ".fuw"}, workingDirWave, dpiScale ); break; case GUI_FILE_WAVE_SAVE_DMW: if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); hasOpened=fileDialog->openSave( "Save Wavetable", {"DefleMask wavetable", ".dmw"}, workingDirWave, dpiScale ); break; case GUI_FILE_WAVE_SAVE_RAW: if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); hasOpened=fileDialog->openSave( "Save Wavetable", {"raw data", ".raw"}, workingDirWave, dpiScale ); break; case GUI_FILE_SAMPLE_OPEN: case GUI_FILE_SAMPLE_OPEN_REPLACE: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); hasOpened=fileDialog->openLoad( "Load Sample", {"compatible files", "*.wav *.dmc *.brr", "all files", "*"}, workingDirSample, dpiScale, NULL, // TODO (type==GUI_FILE_SAMPLE_OPEN) ); break; case GUI_FILE_SAMPLE_OPEN_RAW: case GUI_FILE_SAMPLE_OPEN_REPLACE_RAW: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); hasOpened=fileDialog->openLoad( "Load Raw Sample", {"all files", "*"}, workingDirSample, dpiScale ); break; case GUI_FILE_SAMPLE_SAVE: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); hasOpened=fileDialog->openSave( "Save Sample", {"Wave file", "*.wav"}, workingDirSample, dpiScale ); break; case GUI_FILE_SAMPLE_SAVE_RAW: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); hasOpened=fileDialog->openSave( "Save Raw Sample", {"all files", "*"}, workingDirSample, dpiScale ); break; case GUI_FILE_EXPORT_AUDIO_ONE: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); hasOpened=fileDialog->openSave( "Export Audio", {"Wave file", "*.wav"}, workingDirAudioExport, dpiScale ); break; case GUI_FILE_EXPORT_AUDIO_PER_SYS: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); hasOpened=fileDialog->openSave( "Export Audio", {"Wave file", "*.wav"}, workingDirAudioExport, dpiScale ); break; case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); hasOpened=fileDialog->openSave( "Export Audio", {"Wave file", "*.wav"}, workingDirAudioExport, dpiScale ); break; case GUI_FILE_EXPORT_VGM: if (!dirExists(workingDirVGMExport)) workingDirVGMExport=getHomeDir(); hasOpened=fileDialog->openSave( "Export VGM", {"VGM file", "*.vgm"}, workingDirVGMExport, dpiScale ); break; case GUI_FILE_EXPORT_ZSM: if (!dirExists(workingDirZSMExport)) workingDirZSMExport=getHomeDir(); hasOpened=fileDialog->openSave( "Export ZSM", {"ZSM file", "*.zsm"}, workingDirZSMExport, dpiScale ); break; case GUI_FILE_EXPORT_CMDSTREAM: if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir(); hasOpened=fileDialog->openSave( "Export Command Stream", {"text file", "*.txt"}, workingDirROMExport, dpiScale ); break; case GUI_FILE_EXPORT_CMDSTREAM_BINARY: if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir(); hasOpened=fileDialog->openSave( "Export Command Stream", {"binary file", "*.bin"}, workingDirROMExport, dpiScale ); break; case GUI_FILE_EXPORT_ROM: showError("Coming soon!"); break; case GUI_FILE_LOAD_MAIN_FONT: if (!dirExists(workingDirFont)) workingDirFont=getHomeDir(); hasOpened=fileDialog->openLoad( "Select Font", {"compatible files", "*.ttf *.otf *.ttc"}, workingDirFont, dpiScale ); break; case GUI_FILE_LOAD_HEAD_FONT: if (!dirExists(workingDirFont)) workingDirFont=getHomeDir(); hasOpened=fileDialog->openLoad( "Select Font", {"compatible files", "*.ttf *.otf *.ttc"}, workingDirFont, dpiScale ); break; case GUI_FILE_LOAD_PAT_FONT: if (!dirExists(workingDirFont)) workingDirFont=getHomeDir(); hasOpened=fileDialog->openLoad( "Select Font", {"compatible files", "*.ttf *.otf *.ttc"}, workingDirFont, dpiScale ); break; case GUI_FILE_IMPORT_COLORS: if (!dirExists(workingDirColors)) workingDirColors=getHomeDir(); hasOpened=fileDialog->openLoad( "Select Color File", {"configuration files", "*.cfgc"}, workingDirColors, dpiScale ); break; case GUI_FILE_IMPORT_KEYBINDS: if (!dirExists(workingDirKeybinds)) workingDirKeybinds=getHomeDir(); hasOpened=fileDialog->openLoad( "Select Keybind File", {"configuration files", "*.cfgk"}, workingDirKeybinds, dpiScale ); break; case GUI_FILE_IMPORT_LAYOUT: if (!dirExists(workingDirKeybinds)) workingDirKeybinds=getHomeDir(); hasOpened=fileDialog->openLoad( "Select Layout File", {".ini files", "*.ini"}, workingDirKeybinds, dpiScale ); break; case GUI_FILE_EXPORT_COLORS: if (!dirExists(workingDirColors)) workingDirColors=getHomeDir(); hasOpened=fileDialog->openSave( "Export Colors", {"configuration files", "*.cfgc"}, workingDirColors, dpiScale ); break; case GUI_FILE_EXPORT_KEYBINDS: if (!dirExists(workingDirKeybinds)) workingDirKeybinds=getHomeDir(); hasOpened=fileDialog->openSave( "Export Keybinds", {"configuration files", "*.cfgk"}, workingDirKeybinds, dpiScale ); break; case GUI_FILE_EXPORT_LAYOUT: if (!dirExists(workingDirKeybinds)) workingDirKeybinds=getHomeDir(); hasOpened=fileDialog->openSave( "Export Layout", {".ini files", "*.ini"}, workingDirKeybinds, dpiScale ); break; case GUI_FILE_YRW801_ROM_OPEN: case GUI_FILE_TG100_ROM_OPEN: case GUI_FILE_MU5_ROM_OPEN: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); hasOpened=fileDialog->openLoad( "Load ROM", {"compatible files", "*.rom *.bin", "all files", "*"}, workingDirROM, dpiScale ); break; case GUI_FILE_CMDSTREAM_OPEN: if (!dirExists(workingDirROM)) workingDirROM=getHomeDir(); hasOpened=fileDialog->openLoad( "Play Command Stream", {"command stream", "*.bin", "all files", "*"}, workingDirROM, dpiScale ); break; case GUI_FILE_TEST_OPEN: if (!dirExists(workingDirTest)) workingDirTest=getHomeDir(); hasOpened=fileDialog->openLoad( "Open Test", {"compatible files", "*.fur *.dmf *.mod", "another option", "*.wav *.ttf", "all files", "*"}, workingDirTest, dpiScale, [](const char* path) { if (path!=NULL) { logI("Callback Result: %s",path); } else { logI("Callback Result: NULL"); } } ); break; case GUI_FILE_TEST_OPEN_MULTI: if (!dirExists(workingDirTest)) workingDirTest=getHomeDir(); hasOpened=fileDialog->openLoad( "Open Test (Multi)", {"compatible files", "*.fur *.dmf *.mod", "another option", "*.wav *.ttf", "all files", "*"}, workingDirTest, dpiScale, [](const char* path) { if (path!=NULL) { logI("Callback Result: %s",path); } else { logI("Callback Result: NULL"); } }, true ); break; case GUI_FILE_TEST_SAVE: if (!dirExists(workingDirTest)) workingDirTest=getHomeDir(); hasOpened=fileDialog->openSave( "Save Test", {"Furnace song", "*.fur", "DefleMask module", "*.dmf"}, workingDirTest, dpiScale ); break; } if (hasOpened) curFileDialog=type; //ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard; } int FurnaceGUI::save(String path, int dmfVersion) { SafeWriter* w; logD("saving file..."); if (dmfVersion) { if (dmfVersion<24) dmfVersion=24; w=e->saveDMF(dmfVersion); } else { w=e->saveFur(false,settings.newPatternFormat); } if (w==NULL) { lastError=e->getLastError(); logE("couldn't save! %s",lastError); return 3; } logV("opening file for writing..."); FILE* outFile=ps_fopen(path.c_str(),"wb"); if (outFile==NULL) { lastError=strerror(errno); logE("couldn't save! %s",lastError); w->finish(); return 1; } if (settings.compress) { unsigned char zbuf[131072]; int ret; z_stream zl; memset(&zl,0,sizeof(z_stream)); ret=deflateInit(&zl,Z_DEFAULT_COMPRESSION); if (ret!=Z_OK) { logE("zlib error!"); lastError="compression error"; fclose(outFile); w->finish(); return 2; } zl.avail_in=w->size(); zl.next_in=w->getFinalBuf(); while (zl.avail_in>0) { zl.avail_out=131072; zl.next_out=zbuf; if ((ret=deflate(&zl,Z_NO_FLUSH))==Z_STREAM_ERROR) { logE("zlib stream error!"); lastError="zlib stream error"; deflateEnd(&zl); fclose(outFile); w->finish(); return 2; } size_t amount=131072-zl.avail_out; if (amount>0) { if (fwrite(zbuf,1,amount,outFile)!=amount) { logE("did not write entirely: %s!",strerror(errno)); lastError=strerror(errno); deflateEnd(&zl); fclose(outFile); w->finish(); return 1; } } } zl.avail_out=131072; zl.next_out=zbuf; if ((ret=deflate(&zl,Z_FINISH))==Z_STREAM_ERROR) { logE("zlib finish stream error!"); lastError="zlib finish stream error"; deflateEnd(&zl); fclose(outFile); w->finish(); return 2; } if (131072-zl.avail_out>0) { if (fwrite(zbuf,1,131072-zl.avail_out,outFile)!=(131072-zl.avail_out)) { logE("did not write entirely: %s!",strerror(errno)); lastError=strerror(errno); deflateEnd(&zl); fclose(outFile); w->finish(); return 1; } } deflateEnd(&zl); } else { if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { logE("did not write entirely: %s!",strerror(errno)); lastError=strerror(errno); fclose(outFile); w->finish(); return 1; } } fclose(outFile); w->finish(); backupLock.lock(); curFileName=path; backupLock.unlock(); modified=false; updateWindowTitle(); if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } pushRecentFile(path); pushRecentSys(path.c_str()); logD("save complete."); return 0; } int FurnaceGUI::load(String path) { bool wasPlaying=e->isPlaying(); if (!path.empty()) { logI("loading module..."); FILE* f=ps_fopen(path.c_str(),"rb"); if (f==NULL) { perror("error"); lastError=strerror(errno); return 1; } if (fseek(f,0,SEEK_END)<0) { perror("size error"); lastError=fmt::sprintf("on seek: %s",strerror(errno)); fclose(f); return 1; } ssize_t len=ftell(f); if (len==(SIZE_MAX>>1)) { perror("could not get file length"); lastError=fmt::sprintf("on pre tell: %s",strerror(errno)); fclose(f); return 1; } if (len<1) { if (len==0) { logE("that file is empty!"); lastError="file is empty"; } else { perror("tell error"); lastError=fmt::sprintf("on tell: %s",strerror(errno)); } fclose(f); return 1; } if (fseek(f,0,SEEK_SET)<0) { perror("size error"); lastError=fmt::sprintf("on get size: %s",strerror(errno)); fclose(f); return 1; } unsigned char* file=new unsigned char[len]; if (fread(file,1,(size_t)len,f)!=(size_t)len) { perror("read error"); lastError=fmt::sprintf("on read: %s",strerror(errno)); fclose(f); delete[] file; return 1; } fclose(f); if (!e->load(file,(size_t)len)) { lastError=e->getLastError(); logE("could not open file!"); return 1; } } backupLock.lock(); curFileName=path; backupLock.unlock(); modified=false; curNibble=false; orderNibble=false; orderCursor=-1; curOrder=0; oldRow=0; samplePos=0; updateSampleTex=true; selStart=SelectionPoint(); selEnd=SelectionPoint(); cursor=SelectionPoint(); lastError="everything OK"; undoHist.clear(); redoHist.clear(); updateWindowTitle(); updateScroll(0); if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } pushRecentFile(path); // do not auto-play a backup if (path.find(backupPath)!=0) { if (settings.playOnLoad==2 || (settings.playOnLoad==1 && wasPlaying)) { play(); } } return 0; } void FurnaceGUI::pushRecentFile(String path) { if (path.empty()) return; if (path.find(backupPath)==0) return; for (int i=0; i<(int)recentFile.size(); i++) { if (recentFile[i]==path) { recentFile.erase(recentFile.begin()+i); i--; } } recentFile.push_front(path); while (!recentFile.empty() && (int)recentFile.size()>settings.maxRecentFile) { recentFile.pop_back(); } } void FurnaceGUI::pushRecentSys(const char* path) { #ifdef _WIN32 WString widePath=utf8To16(path); SHAddToRecentDocs(SHARD_PATHW,widePath.c_str()); #endif } void FurnaceGUI::delFirstBackup(String name) { std::vector listOfFiles; #ifdef _WIN32 String findPath=backupPath+String(DIR_SEPARATOR_STR)+name+String("*.fur"); WIN32_FIND_DATAW next; HANDLE backDir=FindFirstFileW(utf8To16(findPath.c_str()).c_str(),&next); if (backDir!=INVALID_HANDLE_VALUE) { do { listOfFiles.push_back(utf16To8(next.cFileName)); } while (FindNextFileW(backDir,&next)!=0); FindClose(backDir); } #else DIR* backDir=opendir(backupPath.c_str()); if (backDir==NULL) { logW("could not open backups dir!"); return; } while (true) { struct dirent* next=readdir(backDir); if (next==NULL) break; if (strstr(next->d_name,name.c_str())!=next->d_name) continue; listOfFiles.push_back(String(next->d_name)); } closedir(backDir); #endif std::sort(listOfFiles.begin(),listOfFiles.end(),[](const String& a, const String& b) -> bool { return strcmp(a.c_str(),b.c_str())<0; }); int totalDelete=((int)listOfFiles.size())-5; for (int i=0; i>1)) { perror("could not get file length"); lastError=fmt::sprintf("on pre tell: %s",strerror(errno)); fclose(f); return 1; } if (len<1) { if (len==0) { logE("that file is empty!"); lastError="file is empty"; } else { perror("tell error"); lastError=fmt::sprintf("on tell: %s",strerror(errno)); } fclose(f); return 1; } if (fseek(f,0,SEEK_SET)<0) { perror("size error"); lastError=fmt::sprintf("on get size: %s",strerror(errno)); fclose(f); return 1; } unsigned char* file=new unsigned char[len]; if (fread(file,1,(size_t)len,f)!=(size_t)len) { perror("read error"); lastError=fmt::sprintf("on read: %s",strerror(errno)); fclose(f); delete[] file; return 1; } fclose(f); if (!e->playStream(file,(size_t)len)) { lastError=e->getLastError(); logE("could not open file!"); return 1; } } return 0; } void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) { e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut); displayExporting=true; } void FurnaceGUI::editStr(String* which) { editString=which; displayEditString=true; } void FurnaceGUI::showWarning(String what, FurnaceGUIWarnings type) { warnString=what; warnAction=type; warnQuit=true; } void FurnaceGUI::showError(String what) { errorString=what; displayError=true; } String FurnaceGUI::getLastError() { return lastError; } // what monster did I just create here? #define B30(tt) (macroDragBit30?((((tt)&0xc0000000)==0x40000000 || ((tt)&0xc0000000)==0x80000000)?0x40000000:0):0) #define MACRO_DRAG(t) \ if (macroDragSettingBit30) { \ if (macroDragLastX!=x || macroDragLastY!=y) { \ macroDragLastX=x; \ macroDragLastY=y; \ if (macroDragInitialValueSet) { \ if (!macroDragInitialValue) { \ if (t[x]&0x80000000) { \ t[x]&=~0x40000000; \ } else { \ t[x]|=0x40000000; \ } \ } else { \ if (t[x]&0x80000000) { \ t[x]|=0x40000000; \ } else { \ t[x]&=~0x40000000; \ } \ } \ } else { \ macroDragInitialValue=(((t[x])&0xc0000000)==0x40000000 || ((t[x])&0xc0000000)==0x80000000); \ macroDragInitialValueSet=true; \ t[x]^=0x40000000; \ } \ } \ } else if (macroDragBitMode) { \ if (macroDragLastX!=x || macroDragLastY!=y) { \ macroDragLastX=x; \ macroDragLastY=y; \ if (macroDragInitialValueSet) { \ if (macroDragInitialValue) { \ t[x]=(((t[x]+macroDragBitOff)&((1<=16.0f) { \ macroDragMouseMoved=true; \ } \ } \ if (macroDragMouseMoved) { \ if ((int)round(x-macroDragLineInitial.x)==0) { \ t[x]=B30(t[x])^(int)(macroDragLineInitial.y); \ } else { \ if ((int)round(x-macroDragLineInitial.x)<0) { \ for (int i=0; i<=(int)round(macroDragLineInitial.x-x); i++) { \ int index=(int)round(x+i); \ if (index<0) continue; \ t[index]=B30(t[index])^(int)(y+(macroDragLineInitial.y-y)*((float)i/(float)(macroDragLineInitial.x-x))); \ } \ } else { \ for (int i=0; i<=(int)round(x-macroDragLineInitial.x); i++) { \ int index=(int)round(i+macroDragLineInitial.x); \ if (index<0) continue; \ t[index]=B30(t[index])^(int)(macroDragLineInitial.y+(y-macroDragLineInitial.y)*((float)i/(x-macroDragLineInitial.x))); \ } \ } \ } \ } \ } else { \ t[x]=B30(t[x])^(y); \ } \ } void FurnaceGUI::processDrags(int dragX, int dragY) { if (macroDragActive) { if (macroDragLen>0) { int x=((dragX-macroDragStart.x)*macroDragLen/MAX(1,macroDragAreaSize.x)); if (x<0) x=0; if (x>=macroDragLen) x=macroDragLen-1; x+=macroDragScroll; int y; if (macroDragBitMode) { y=(int)(macroDragMax-((dragY-macroDragStart.y)*(double(macroDragMax-macroDragMin)/(double)MAX(1,macroDragAreaSize.y)))); } else { y=round(macroDragMax-((dragY-macroDragStart.y)*(double(macroDragMax-macroDragMin)/(double)MAX(1,macroDragAreaSize.y)))); } if (y>macroDragMax) y=macroDragMax; if (y0) { int x=(dragX-macroLoopDragStart.x)*macroLoopDragLen/MAX(1,macroLoopDragAreaSize.x); if (x<0) x=0; if (x>=macroLoopDragLen) { x=-1; } else { x+=macroDragScroll; } *macroLoopDragTarget=x; } } if (waveDragActive) { if (waveDragLen>0) { int x=(dragX-waveDragStart.x)*waveDragLen/MAX(1,waveDragAreaSize.x); if (x<0) x=0; if (x>=waveDragLen) x=waveDragLen-1; int y=round(waveDragMax-((dragY-waveDragStart.y)*(double(waveDragMax-waveDragMin)/(double)MAX(1,waveDragAreaSize.y)))); if (y>waveDragMax) y=waveDragMax; if (y=(int)sampleDragLen) x=sampleDragLen-1; } else { if (x>(int)sampleDragLen) x=sampleDragLen; } if (x1<0) x1=0; if (x1>=(int)sampleDragLen) x1=sampleDragLen-1; double y=0.5-double(dragY-sampleDragStart.y)/sampleDragAreaSize.y; if (sampleDragMode) { // draw if (sampleDragTarget) { if (sampleDrag16) { int val=y*65536; if (val<-32768) val=-32768; if (val>32767) val=32767; for (int i=x; i<=x1; i++) ((short*)sampleDragTarget)[i]=val; } else { int val=y*256; if (val<-128) val=-128; if (val>127) val=127; for (int i=x; i<=x1; i++) ((signed char*)sampleDragTarget)[i]=val; } updateSampleTex=true; } } else { // select if (sampleSelStart<0) { sampleSelStart=x; } sampleSelEnd=x; } } if (orderScrollLocked) { if (fabs(orderScrollRealOrigin.x-dragX)>2.0f*dpiScale || fabs(orderScrollRealOrigin.y-dragY)>2.0f*dpiScale) orderScrollTolerance=false; orderScroll=(orderScrollSlideOrigin-dragX)/(40.0*dpiScale); if (orderScroll<0.0f) orderScroll=0.0f; if (orderScroll>(float)e->curSubSong->ordersLen-1) orderScroll=e->curSubSong->ordersLen-1; } } #define checkExtension(x) \ String lowerCase=fileName; \ for (char& i: lowerCase) { \ if (i>='A' && i<='Z') i+='a'-'A'; \ } \ if (lowerCase.size()='A' && i<='Z') i+='a'-'A'; \ } \ if (lowerCase.size()<4 || (lowerCase.rfind(x)!=lowerCase.size()-4 && lowerCase.rfind(y)!=lowerCase.size()-4)) { \ fileName+=fallback; \ } #define checkExtensionTriple(x,y,z,fallback) \ String lowerCase=fileName; \ for (char& i: lowerCase) { \ if (i>='A' && i<='Z') i+='a'-'A'; \ } \ if (lowerCase.size()<4 || (lowerCase.rfind(x)!=lowerCase.size()-4 && lowerCase.rfind(y)!=lowerCase.size()-4 && lowerCase.rfind(z)!=lowerCase.size()-4)) { \ fileName+=fallback; \ } #define drawOpMask(m) \ ImGui::PushFont(patFont); \ ImGui::PushID("om_" #m); \ if (ImGui::BeginTable("opMaskTable",5,ImGuiTableFlags_Borders|ImGuiTableFlags_SizingFixedFit|ImGuiTableFlags_NoHostExtendX)) { \ ImGui::TableNextRow(); \ ImGui::TableNextColumn(); \ ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_ACTIVE]); \ if (ImGui::Selectable(m.note?"C-4##opMaskNote":"---##opMaskNote",m.note,ImGuiSelectableFlags_DontClosePopups)) { \ m.note=!m.note; \ } \ ImGui::PopStyleColor(); \ ImGui::TableNextColumn(); \ ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS]); \ if (ImGui::Selectable(m.ins?"01##opMaskIns":"--##opMaskIns",m.ins,ImGuiSelectableFlags_DontClosePopups)) { \ m.ins=!m.ins; \ } \ ImGui::PopStyleColor(); \ ImGui::TableNextColumn(); \ ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_VOLUME_MAX]); \ if (ImGui::Selectable(m.vol?"7F##opMaskVol":"--##opMaskVol",m.vol,ImGuiSelectableFlags_DontClosePopups)) { \ m.vol=!m.vol; \ } \ ImGui::PopStyleColor(); \ ImGui::TableNextColumn(); \ ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH]); \ if (ImGui::Selectable(m.effect?"04##opMaskEffect":"--##opMaskEffect",m.effect,ImGuiSelectableFlags_DontClosePopups)) { \ m.effect=!m.effect; \ } \ ImGui::TableNextColumn(); \ if (ImGui::Selectable(m.effectVal?"72##opMaskEffectVal":"--##opMaskEffectVal",m.effectVal,ImGuiSelectableFlags_DontClosePopups)) { \ m.effectVal=!m.effectVal; \ } \ ImGui::PopStyleColor(); \ ImGui::EndTable(); \ } \ ImGui::PopID(); \ ImGui::PopFont(); void FurnaceGUI::editOptions(bool topMenu) { char id[4096]; editOptsVisible=true; if (ImGui::MenuItem("cut",BIND_FOR(GUI_ACTION_PAT_CUT))) doCopy(true,true,selStart,selEnd); if (ImGui::MenuItem("copy",BIND_FOR(GUI_ACTION_PAT_COPY))) doCopy(false,true,selStart,selEnd); if (ImGui::MenuItem("paste",BIND_FOR(GUI_ACTION_PAT_PASTE))) doPaste(); if (!basicMode) if (ImGui::BeginMenu("paste special...")) { if (ImGui::MenuItem("paste mix",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX))) doPaste(GUI_PASTE_MODE_MIX_FG); if (ImGui::MenuItem("paste mix (background)",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX_BG))) doPaste(GUI_PASTE_MODE_MIX_BG); if (ImGui::BeginMenu("paste with ins (foreground)")) { if (e->song.ins.empty()) { ImGui::Text("no instruments available"); } for (size_t i=0; isong.ins.size(); i++) { snprintf(id,4095,"%.2X: %s",(int)i,e->song.ins[i]->name.c_str()); if (ImGui::MenuItem(id)) { doPaste(GUI_PASTE_MODE_INS_FG,i); } } ImGui::EndMenu(); } if (ImGui::BeginMenu("paste with ins (background)")) { if (e->song.ins.empty()) { ImGui::Text("no instruments available"); } for (size_t i=0; isong.ins.size(); i++) { snprintf(id,4095,"%.2X: %s",(int)i,e->song.ins[i]->name.c_str()); if (ImGui::MenuItem(id)) { doPaste(GUI_PASTE_MODE_INS_BG,i); } } ImGui::EndMenu(); } if (ImGui::MenuItem("paste flood",BIND_FOR(GUI_ACTION_PAT_PASTE_FLOOD))) doPaste(GUI_PASTE_MODE_FLOOD); if (ImGui::MenuItem("paste overflow",BIND_FOR(GUI_ACTION_PAT_PASTE_OVERFLOW))) doPaste(GUI_PASTE_MODE_OVERFLOW); ImGui::EndMenu(); } if (ImGui::MenuItem("delete",BIND_FOR(GUI_ACTION_PAT_DELETE))) doDelete(); if (topMenu) { if (ImGui::MenuItem("select all",BIND_FOR(GUI_ACTION_PAT_SELECT_ALL))) doSelectAll(); } ImGui::Separator(); if (!basicMode) { if (ImGui::BeginMenu("operation mask...")) { drawOpMask(opMaskDelete); ImGui::SameLine(); ImGui::Text("delete"); drawOpMask(opMaskPullDelete); ImGui::SameLine(); ImGui::Text("pull delete"); drawOpMask(opMaskInsert); ImGui::SameLine(); ImGui::Text("insert"); drawOpMask(opMaskPaste); ImGui::SameLine(); ImGui::Text("paste"); drawOpMask(opMaskTransposeNote); ImGui::SameLine(); ImGui::Text("transpose (note)"); drawOpMask(opMaskTransposeValue); ImGui::SameLine(); ImGui::Text("transpose (value)"); drawOpMask(opMaskInterpolate); ImGui::SameLine(); ImGui::Text("interpolate"); drawOpMask(opMaskFade); ImGui::SameLine(); ImGui::Text("fade"); drawOpMask(opMaskInvertVal); ImGui::SameLine(); ImGui::Text("invert values"); drawOpMask(opMaskScale); ImGui::SameLine(); ImGui::Text("scale"); drawOpMask(opMaskRandomize); ImGui::SameLine(); ImGui::Text("randomize"); drawOpMask(opMaskFlip); ImGui::SameLine(); ImGui::Text("flip"); drawOpMask(opMaskCollapseExpand); ImGui::SameLine(); ImGui::Text("collapse/expand"); ImGui::EndMenu(); } ImGui::Text("input latch"); ImGui::PushFont(patFont); if (ImGui::BeginTable("inputLatchTable",5,ImGuiTableFlags_Borders|ImGuiTableFlags_SizingFixedFit|ImGuiTableFlags_NoHostExtendX)) { static char id[64]; ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_ACTIVE]); ImGui::Text("C-4"); ImGui::PopStyleColor(); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS]); if (latchIns==-2) { strcpy(id,"&&##LatchIns"); } else if (latchIns==-1) { strcpy(id,"..##LatchIns"); } else { snprintf(id,63,"%.2x##LatchIns",latchIns&0xff); } if (ImGui::Selectable(id,latchTarget==1,ImGuiSelectableFlags_DontClosePopups)) { latchTarget=1; latchNibble=false; } if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { latchIns=-2; } if (ImGui::IsItemHovered()) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); ImGui::SetTooltip("&&: selected instrument\n..: no instrument"); ImGui::PopStyleColor(); } ImGui::PopStyleColor(); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_VOLUME_MAX]); if (latchVol==-1) { strcpy(id,"..##LatchVol"); } else { snprintf(id,63,"%.2x##LatchVol",latchVol&0xff); } if (ImGui::Selectable(id,latchTarget==2,ImGuiSelectableFlags_DontClosePopups)) { latchTarget=2; latchNibble=false; } if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { latchVol=-1; } ImGui::PopStyleColor(); ImGui::TableNextColumn(); if (latchEffect==-1) { strcpy(id,"..##LatchFX"); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INACTIVE]); } else { const unsigned char data=latchEffect; snprintf(id,63,"%.2x##LatchFX",data); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[data]]); } if (ImGui::Selectable(id,latchTarget==3,ImGuiSelectableFlags_DontClosePopups)) { latchTarget=3; latchNibble=false; } if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { latchEffect=-1; } ImGui::TableNextColumn(); if (latchEffectVal==-1) { strcpy(id,"..##LatchFXV"); } else { snprintf(id,63,"%.2x##LatchFXV",latchEffectVal&0xff); } if (ImGui::Selectable(id,latchTarget==4,ImGuiSelectableFlags_DontClosePopups)) { latchTarget=4; latchNibble=false; } if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { latchEffectVal=-1; } ImGui::PopStyleColor(); ImGui::EndTable(); } ImGui::PopFont(); ImGui::SameLine(); if (ImGui::Button("Set")) { DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true); latchIns=pat->data[cursor.y][2]; latchVol=pat->data[cursor.y][3]; latchEffect=pat->data[cursor.y][4]; latchEffectVal=pat->data[cursor.y][5]; latchTarget=0; latchNibble=false; } ImGui::SameLine(); if (ImGui::Button("Reset")) { latchIns=-2; latchVol=-1; latchEffect=-1; latchEffectVal=-1; latchTarget=0; latchNibble=false; } ImGui::Separator(); } if (ImGui::MenuItem("note up",BIND_FOR(GUI_ACTION_PAT_NOTE_UP))) doTranspose(1,opMaskTransposeNote); if (ImGui::MenuItem("note down",BIND_FOR(GUI_ACTION_PAT_NOTE_DOWN))) doTranspose(-1,opMaskTransposeNote); if (ImGui::MenuItem("octave up",BIND_FOR(GUI_ACTION_PAT_OCTAVE_UP))) doTranspose(12,opMaskTransposeNote); if (ImGui::MenuItem("octave down",BIND_FOR(GUI_ACTION_PAT_OCTAVE_DOWN))) doTranspose(-12,opMaskTransposeNote); ImGui::Separator(); if (ImGui::MenuItem("values up",BIND_FOR(GUI_ACTION_PAT_VALUE_UP))) doTranspose(1,opMaskTransposeValue); if (ImGui::MenuItem("values down",BIND_FOR(GUI_ACTION_PAT_VALUE_DOWN))) doTranspose(-1,opMaskTransposeValue); if (ImGui::MenuItem("values up (+16)",BIND_FOR(GUI_ACTION_PAT_VALUE_UP_COARSE))) doTranspose(16,opMaskTransposeValue); if (ImGui::MenuItem("values down (-16)",BIND_FOR(GUI_ACTION_PAT_VALUE_DOWN_COARSE))) doTranspose(-16,opMaskTransposeValue); ImGui::Separator(); ImGui::AlignTextToFramePadding(); ImGui::Text("transpose"); ImGui::SameLine(); ImGui::SetNextItemWidth(120.0f*dpiScale); if (ImGui::InputInt("##TransposeAmount",&transposeAmount,1,1)) { if (transposeAmount<-96) transposeAmount=-96; if (transposeAmount>96) transposeAmount=96; } ImGui::SameLine(); if (ImGui::Button("Notes")) { doTranspose(transposeAmount,opMaskTransposeNote); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Values")) { doTranspose(transposeAmount,opMaskTransposeValue); ImGui::CloseCurrentPopup(); } ImGui::Separator(); if (ImGui::MenuItem("interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE))) doInterpolate(); if (ImGui::BeginMenu("change instrument...")) { if (e->song.ins.empty()) { ImGui::Text("no instruments available"); } for (size_t i=0; isong.ins.size(); i++) { snprintf(id,4095,"%.2X: %s",(int)i,e->song.ins[i]->name.c_str()); if (ImGui::MenuItem(id)) { doChangeIns(i); } } ImGui::EndMenu(); } if (!basicMode) { if (ImGui::BeginMenu("gradient/fade...")) { if (ImGui::InputInt("Start",&fadeMin,1,1)) { if (fadeMin<0) fadeMin=0; if (fadeMode) { if (fadeMin>15) fadeMin=15; } else { if (fadeMin>255) fadeMin=255; } } if (ImGui::InputInt("End",&fadeMax,1,1)) { if (fadeMax<0) fadeMax=0; if (fadeMode) { if (fadeMax>15) fadeMax=15; } else { if (fadeMax>255) fadeMax=255; } } if (ImGui::Checkbox("Nibble mode",&fadeMode)) { if (fadeMode) { if (fadeMin>15) fadeMin=15; if (fadeMax>15) fadeMax=15; } else { if (fadeMin>255) fadeMin=255; if (fadeMax>255) fadeMax=255; } } if (ImGui::Button("Go ahead")) { doFade(fadeMin,fadeMax,fadeMode); ImGui::CloseCurrentPopup(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("scale...")) { if (ImGui::InputFloat("##ScaleMax",&scaleMax,1,1,"%.1f%%")) { if (scaleMax<0.0f) scaleMax=0.0f; if (scaleMax>25600.0f) scaleMax=25600.0f; } if (ImGui::Button("Scale")) { doScale(scaleMax); ImGui::CloseCurrentPopup(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("randomize...")) { if (ImGui::InputInt("Minimum",&randomizeMin,1,1)) { if (randomizeMin<0) randomizeMin=0; if (randomMode) { if (randomizeMin>15) randomizeMin=15; } else { if (randomizeMin>255) randomizeMin=255; } if (randomizeMin>randomizeMax) randomizeMin=randomizeMax; } if (ImGui::InputInt("Maximum",&randomizeMax,1,1)) { if (randomizeMax<0) randomizeMax=0; if (randomizeMax15) randomizeMax=15; } else { if (randomizeMax>255) randomizeMax=255; } } if (ImGui::Checkbox("Nibble mode",&randomMode)) { if (randomMode) { if (randomizeMin>15) randomizeMin=15; if (randomizeMax>15) randomizeMax=15; } else { if (randomizeMin>255) randomizeMin=255; if (randomizeMax>255) randomizeMax=255; } } // TODO: add an option to set effect to specific value? if (ImGui::Button("Randomize")) { doRandomize(randomizeMin,randomizeMax,randomMode); ImGui::CloseCurrentPopup(); } ImGui::EndMenu(); } if (ImGui::MenuItem("invert values",BIND_FOR(GUI_ACTION_PAT_INVERT_VALUES))) doInvertValues(); ImGui::Separator(); if (ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION))) doFlip(); ImGui::SetNextItemWidth(120.0f*dpiScale); if (ImGui::InputInt("collapse/expand amount##CollapseAmount",&collapseAmount,1,1)) { if (collapseAmount<2) collapseAmount=2; if (collapseAmount>256) collapseAmount=256; } if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(collapseAmount,selStart,selEnd); if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(collapseAmount,selStart,selEnd); if (topMenu) { ImGui::Separator(); if (ImGui::MenuItem("collapse pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT))) doAction(GUI_ACTION_PAT_COLLAPSE_PAT); if (ImGui::MenuItem("expand pattern",BIND_FOR(GUI_ACTION_PAT_EXPAND_PAT))) doAction(GUI_ACTION_PAT_EXPAND_PAT); } } if (topMenu) { ImGui::Separator(); if (ImGui::MenuItem("collapse song",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_SONG))) doAction(GUI_ACTION_PAT_COLLAPSE_SONG); if (ImGui::MenuItem("expand song",BIND_FOR(GUI_ACTION_PAT_EXPAND_SONG))) doAction(GUI_ACTION_PAT_EXPAND_SONG); } if (!basicMode) { if (topMenu) { ImGui::Separator(); if (ImGui::MenuItem("find/replace",BIND_FOR(GUI_ACTION_WINDOW_FIND),findOpen)) { if (findOpen) { findOpen=false; } else { nextWindow=GUI_WINDOW_FIND; } } } } } void FurnaceGUI::toggleMobileUI(bool enable, bool force) { if (mobileUI!=enable || force) { if (!mobileUI && enable) { if (!ImGui::SaveIniSettingsToDisk(finalLayoutPath,true)) { reportError(fmt::sprintf("could NOT save layout! %s",strerror(errno))); } } mobileUI=enable; if (mobileUI) { ImGui::GetIO().IniFilename=NULL; ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_InertialScrollEnable; ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NoHoverColors; fileDialog->mobileUI=true; } else { ImGui::GetIO().IniFilename=NULL; if (!ImGui::LoadIniSettingsFromDisk(finalLayoutPath,true)) { reportError(fmt::sprintf("could NOT load layout! %s",strerror(errno))); ImGui::LoadIniSettingsFromMemory(defaultLayout); } ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_InertialScrollEnable; ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NoHoverColors; fileDialog->mobileUI=false; } } } void FurnaceGUI::pushToggleColors(bool status) { ImVec4 toggleColor=status?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF]; ImGui::PushStyleColor(ImGuiCol_Button,toggleColor); if (!mobileUI) { if (settings.guiColorsBase) { toggleColor.x*=0.8f; toggleColor.y*=0.8f; toggleColor.z*=0.8f; } else { toggleColor.x=CLAMP(toggleColor.x*1.3f,0.0f,1.0f); toggleColor.y=CLAMP(toggleColor.y*1.3f,0.0f,1.0f); toggleColor.z=CLAMP(toggleColor.z*1.3f,0.0f,1.0f); } } ImGui::PushStyleColor(ImGuiCol_ButtonHovered,toggleColor); if (settings.guiColorsBase) { toggleColor.x*=0.8f; toggleColor.y*=0.8f; toggleColor.z*=0.8f; } else { toggleColor.x=CLAMP(toggleColor.x*1.5f,0.0f,1.0f); toggleColor.y=CLAMP(toggleColor.y*1.5f,0.0f,1.0f); toggleColor.z=CLAMP(toggleColor.z*1.5f,0.0f,1.0f); } ImGui::PushStyleColor(ImGuiCol_ButtonActive,toggleColor); } void FurnaceGUI::popToggleColors() { ImGui::PopStyleColor(3); } int _processEvent(void* instance, SDL_Event* event) { return ((FurnaceGUI*)instance)->processEvent(event); } #if SDL_VERSION_ATLEAST(2,0,17) #define VALID_MODS KMOD_NUM|KMOD_CAPS|KMOD_SCROLL #else #define VALID_MODS KMOD_NUM|KMOD_CAPS #endif int FurnaceGUI::processEvent(SDL_Event* ev) { if (introPos<11.0 && !shortIntro) return 1; #ifdef IS_MOBILE if (ev->type==SDL_APP_TERMINATING) { // TODO: save last song state here } else if (ev->type==SDL_APP_WILLENTERBACKGROUND) { commitState(); e->saveConf(); } #endif if (ev->type==SDL_KEYDOWN) { if (!ev->key.repeat && latchTarget==0 && !wantCaptureKeyboard && !sampleMapWaitingInput && (ev->key.keysym.mod&(~(VALID_MODS)))==0) { if (settings.notePreviewBehavior==0) return 1; switch (curWindow) { case GUI_WINDOW_SAMPLE_EDIT: case GUI_WINDOW_SAMPLE_LIST: { auto it=noteKeys.find(ev->key.keysym.scancode); if (it!=noteKeys.cend()) { int key=it->second; int num=12*curOctave+key; if (key!=100 && key!=101 && key!=102) { int pStart=-1; int pEnd=-1; if (curWindow==GUI_WINDOW_SAMPLE_EDIT) { if (sampleSelStart!=sampleSelEnd) { pStart=sampleSelStart; pEnd=sampleSelEnd; if (pStart>pEnd) { pStart^=pEnd; pEnd^=pStart; pStart^=pEnd; } } } e->previewSample(curSample,num,pStart,pEnd); samplePreviewOn=true; samplePreviewKey=ev->key.keysym.scancode; samplePreviewNote=num; } } break; } case GUI_WINDOW_WAVE_LIST: case GUI_WINDOW_WAVE_EDIT: { auto it=noteKeys.find(ev->key.keysym.scancode); if (it!=noteKeys.cend()) { int key=it->second; int num=12*curOctave+key; if (key!=100 && key!=101 && key!=102) { e->previewWave(curWave,num); wavePreviewOn=true; wavePreviewKey=ev->key.keysym.scancode; wavePreviewNote=num; } } break; } case GUI_WINDOW_ORDERS: // ignore here break; case GUI_WINDOW_PATTERN: if (settings.notePreviewBehavior==1) { if (cursor.xFine!=0) break; } else if (settings.notePreviewBehavior==2) { if (edit && cursor.xFine!=0) break; } // fall-through default: { auto it=noteKeys.find(ev->key.keysym.scancode); if (it!=noteKeys.cend()) { int key=it->second; int num=12*curOctave+key; if (num<-60) num=-60; // C-(-5) if (num>119) num=119; // B-9 if (key!=100 && key!=101 && key!=102) { previewNote(cursor.xCoarse,num); } } break; } } } } else if (ev->type==SDL_KEYUP) { stopPreviewNote(ev->key.keysym.scancode,true); if (wavePreviewOn) { if (ev->key.keysym.scancode==wavePreviewKey) { wavePreviewOn=false; e->stopWavePreview(); } } if (samplePreviewOn) { if (ev->key.keysym.scancode==samplePreviewKey) { samplePreviewOn=false; e->stopSamplePreview(); } } } return 1; } #define FIND_POINT(p,pid) \ for (TouchPoint& i: activePoints) { \ if (i.id==pid) { \ p=&i; \ } \ } void FurnaceGUI::processPoint(SDL_Event& ev) { switch (ev.type) { case SDL_MOUSEMOTION: { TouchPoint* point=NULL; FIND_POINT(point,-1); if (point!=NULL) { point->x=(double)ev.motion.x*((double)canvasW/(double)scrW); point->y=(double)ev.motion.y*((double)canvasH/(double)scrH); } break; } case SDL_MOUSEBUTTONDOWN: { if (ev.button.button!=SDL_BUTTON_LEFT) break; for (size_t i=0; ix; float prevY=point->y; point->x=ev.tfinger.x*canvasW; point->y=ev.tfinger.y*canvasH; point->z=ev.tfinger.pressure; if (point->id==0) { ImGui::GetIO().AddMousePosEvent(point->x,point->y); pointMotion(point->x,point->y,point->x-prevX,point->y-prevY); } } break; } case SDL_FINGERDOWN: { for (size_t i=0; irenderSamplesP(); } else { if (sampleSelStart>sampleSelEnd) { sampleSelStart^=sampleSelEnd; sampleSelEnd^=sampleSelStart; sampleSelStart^=sampleSelEnd; } } } sampleDragActive=false; if (orderScrollLocked) { int targetOrder=round(orderScroll); if (orderScrollTolerance) { targetOrder=round(orderScroll+(orderScrollRealOrigin.x-((float)canvasW/2.0f))/(40.0f*dpiScale)); } if (targetOrder<0) targetOrder=0; if (targetOrder>e->curSubSong->ordersLen-1) targetOrder=e->curSubSong->ordersLen-1; if (curOrder!=targetOrder) setOrder(targetOrder); } orderScrollLocked=false; orderScrollTolerance=false; if (dragMobileMenu) { dragMobileMenu=false; if (mobileMenuOpen) { mobileMenuOpen=(mobileMenuPos>=0.85f); } else { mobileMenuOpen=(mobileMenuPos>=0.15f); } } if (dragMobileEditButton) { dragMobileEditButton=false; } } void FurnaceGUI::pointMotion(int x, int y, int xrel, int yrel) { if (selecting) { // detect whether we have to scroll if (ypatWindowPos.y+patWindowSize.y-2.0f*dpiScale) { addScroll(1); } } if (macroDragActive || macroLoopDragActive || waveDragActive || sampleDragActive || orderScrollLocked) { int distance=fabs((double)xrel); if (distance<1) distance=1; float start=x-xrel; float end=x; float startY=y-yrel; float endY=y; for (int i=0; i<=distance; i++) { float fraction=(float)i/(float)distance; float x=start+(end-start)*fraction; float y=startY+(endY-startY)*fraction; processDrags(x,y); } } } // how many pixels should be visible at least at x/y dir #define OOB_PIXELS_SAFETY 25 bool FurnaceGUI::detectOutOfBoundsWindow(SDL_Rect& failing) { int count=SDL_GetNumVideoDisplays(); if (count<1) { logW("bounds check: error: %s",SDL_GetError()); return false; } SDL_Rect rect; for (int i=0; i=scrX); bool ybound=((rect.y+OOB_PIXELS_SAFETY)<=(scrY+scrH)) && ((rect.y+rect.h-OOB_PIXELS_SAFETY)>=scrY); logD("bounds check: display %d is at %dx%dx%dx%d: %s%s",i,rect.x+OOB_PIXELS_SAFETY,rect.y+OOB_PIXELS_SAFETY,rect.x+rect.w-OOB_PIXELS_SAFETY,rect.y+rect.h-OOB_PIXELS_SAFETY,xbound?"x":"",ybound?"y":""); if (xbound && ybound) { return true; } } failing=rect; return false; } #define DECLARE_METRIC(_n) \ int __perfM##_n; #define MEASURE_BEGIN(_n) \ __perfM##_n=SDL_GetPerformanceCounter(); #define MEASURE_END(_n) \ if (perfMetricsLen<64) { \ perfMetrics[perfMetricsLen++]=FurnaceGUIPerfMetric(#_n,SDL_GetPerformanceCounter()-__perfM##_n); \ } #define MEASURE(_n,_x) \ MEASURE_BEGIN(_n) \ _x; \ MEASURE_END(_n) bool FurnaceGUI::loop() { DECLARE_METRIC(calcChanOsc) DECLARE_METRIC(mobileControls) DECLARE_METRIC(mobileOrderSel) DECLARE_METRIC(subSongs) DECLARE_METRIC(findReplace) DECLARE_METRIC(spoiler) DECLARE_METRIC(pattern) DECLARE_METRIC(editControls) DECLARE_METRIC(speed) DECLARE_METRIC(grooves) DECLARE_METRIC(songInfo) DECLARE_METRIC(orders) DECLARE_METRIC(intro) DECLARE_METRIC(sampleList) DECLARE_METRIC(sampleEdit) DECLARE_METRIC(waveList) DECLARE_METRIC(waveEdit) DECLARE_METRIC(insList) DECLARE_METRIC(insEdit) DECLARE_METRIC(mixer) DECLARE_METRIC(readOsc) DECLARE_METRIC(osc) DECLARE_METRIC(chanOsc) DECLARE_METRIC(volMeter) DECLARE_METRIC(settings) DECLARE_METRIC(debug) DECLARE_METRIC(stats) DECLARE_METRIC(compatFlags) DECLARE_METRIC(piano) DECLARE_METRIC(notes) DECLARE_METRIC(channels) DECLARE_METRIC(patManager) DECLARE_METRIC(sysManager) DECLARE_METRIC(clock) DECLARE_METRIC(regView) DECLARE_METRIC(log) DECLARE_METRIC(effectList) DECLARE_METRIC(popup) #ifdef IS_MOBILE bool doThreadedInput=true; #else bool doThreadedInput=!settings.noThreadedInput; #endif if (doThreadedInput) { logD("key input: event filter"); SDL_SetEventFilter(_processEvent,this); } else { logD("key input: main thread"); } while (!quit) { SDL_Event ev; if (e->isPlaying()) { WAKE_UP; } if (--drawHalt<=0) { drawHalt=0; if (settings.powerSave) SDL_WaitEventTimeout(NULL,500); } memcpy(perfMetricsLast,perfMetrics,64*sizeof(FurnaceGUIPerfMetric)); perfMetricsLastLen=perfMetricsLen; perfMetricsLen=0; eventTimeBegin=SDL_GetPerformanceCounter(); bool updateWindow=false; if (injectBackUp) { ImGui::GetIO().AddKeyEvent(ImGuiKey_Backspace,false); injectBackUp=false; } while (SDL_PollEvent(&ev)) { WAKE_UP; ImGui_ImplSDL2_ProcessEvent(&ev); processPoint(ev); if (!doThreadedInput) processEvent(&ev); switch (ev.type) { case SDL_MOUSEMOTION: { int motionX=(double)ev.motion.x*((double)canvasW/(double)scrW); int motionY=(double)ev.motion.y*((double)canvasH/(double)scrH); int motionXrel=(double)ev.motion.xrel*((double)canvasW/(double)scrW); int motionYrel=(double)ev.motion.yrel*((double)canvasH/(double)scrH); pointMotion(motionX,motionY,motionXrel,motionYrel); break; } case SDL_MOUSEBUTTONUP: pointUp(ev.button.x,ev.button.y,ev.button.button); break; case SDL_MOUSEBUTTONDOWN: pointDown(ev.button.x,ev.button.y,ev.button.button); break; case SDL_MOUSEWHEEL: wheelX+=ev.wheel.x; wheelY+=ev.wheel.y; break; case SDL_WINDOWEVENT: switch (ev.window.event) { case SDL_WINDOWEVENT_RESIZED: scrW=ev.window.data1; scrH=ev.window.data2; portrait=(scrWresized(ev); break; case SDL_WINDOWEVENT_MOVED: scrX=ev.window.data1; scrY=ev.window.data2; updateWindow=true; shallDetectScale=2; logV("window moved to %dx%d",scrX,scrY); break; case SDL_WINDOWEVENT_SIZE_CHANGED: logV("window size changed to %dx%d",ev.window.data1,ev.window.data2); break; case SDL_WINDOWEVENT_MINIMIZED: logV("window minimized"); break; case SDL_WINDOWEVENT_MAXIMIZED: scrMax=true; updateWindow=true; logV("window maximized"); break; case SDL_WINDOWEVENT_RESTORED: scrMax=false; updateWindow=true; logV("window restored"); break; case SDL_WINDOWEVENT_SHOWN: logV("window shown"); break; case SDL_WINDOWEVENT_HIDDEN: logV("window hidden"); break; case SDL_WINDOWEVENT_EXPOSED: logV("window exposed"); break; } break; #if SDL_VERSION_ATLEAST(2,0,4) case SDL_RENDER_DEVICE_RESET: killGraphics=true; break; #endif #if SDL_VERSION_ATLEAST(2,0,17) case SDL_DISPLAYEVENT: { switch (ev.display.event) { case SDL_DISPLAYEVENT_CONNECTED: logD("display %d connected!",ev.display.display); updateWindow=true; shallDetectScale=16; break; case SDL_DISPLAYEVENT_DISCONNECTED: logD("display %d disconnected!",ev.display.display); updateWindow=true; shallDetectScale=16; break; case SDL_DISPLAYEVENT_ORIENTATION: logD("display oriented to %d",ev.display.data1); updateWindow=true; break; } break; } #endif case SDL_KEYDOWN: if (!ImGui::GetIO().WantCaptureKeyboard) { keyDown(ev); } #ifdef IS_MOBILE injectBackUp=true; #endif break; case SDL_KEYUP: // for now break; case SDL_DROPFILE: if (ev.drop.file!=NULL) { int sampleCountBefore=e->song.sampleLen; std::vector instruments=e->instrumentFromFile(ev.drop.file); DivWavetable* droppedWave=NULL; DivSample* droppedSample=NULL; if (!instruments.empty()) { if (e->song.sampleLen!=sampleCountBefore) { e->renderSamplesP(); } if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } for (DivInstrument* i: instruments) { e->addInstrumentPtr(i); } nextWindow=GUI_WINDOW_INS_LIST; MARK_MODIFIED; } else if ((droppedWave=e->waveFromFile(ev.drop.file,false))!=NULL) { e->addWavePtr(droppedWave); nextWindow=GUI_WINDOW_WAVE_LIST; MARK_MODIFIED; } else if ((droppedSample=e->sampleFromFile(ev.drop.file))!=NULL) { e->addSamplePtr(droppedSample); nextWindow=GUI_WINDOW_SAMPLE_LIST; MARK_MODIFIED; } else if (modified) { nextFile=ev.drop.file; showWarning("Unsaved changes! Save changes before opening file?",GUI_WARN_OPEN_DROP); } else { if (load(ev.drop.file)>0) { showError(fmt::sprintf("Error while loading file! (%s)",lastError)); } } SDL_free(ev.drop.file); } break; case SDL_QUIT: if (modified) { showWarning("Unsaved changes! Save changes before quitting?",GUI_WARN_QUIT); } else { quit=true; return true; } break; } } // update config x/y/w/h values based on scrMax state if (updateWindow) { logV("updateWindow is true"); if (!scrMax && !fullScreen) { logV("updating scrConf"); scrConfX=scrX; scrConfY=scrY; scrConfW=scrW; scrConfH=scrH; } } // update canvas size as well if (!rend->getOutputSize(canvasW,canvasH)) { logW("loop: error while getting output size!"); } else { //logV("updateWindow: canvas size %dx%d",canvasW,canvasH); // and therefore window size int prevScrW=scrW; int prevScrH=scrH; SDL_GetWindowSize(sdlWin,&scrW,&scrH); if (prevScrW!=scrW || prevScrH!=scrH) { logV("size change 2: %dx%d (from %dx%d)",scrW,scrH,prevScrW,prevScrH); } ImGui::GetIO().InputScale=(float)canvasW/(float)scrW; } wantCaptureKeyboard=ImGui::GetIO().WantTextInput; if (wantCaptureKeyboard!=oldWantCaptureKeyboard) { oldWantCaptureKeyboard=wantCaptureKeyboard; if (wantCaptureKeyboard) { SDL_StartTextInput(); } else { SDL_StopTextInput(); } } if (wantCaptureKeyboard) { WAKE_UP; } if (ImGui::GetIO().IsSomethingHappening) { WAKE_UP; } if (ImGui::GetIO().MouseDown[0] || ImGui::GetIO().MouseDown[1] || ImGui::GetIO().MouseDown[2] || ImGui::GetIO().MouseDown[3] || ImGui::GetIO().MouseDown[4]) { WAKE_UP; } while (true) { midiLock.lock(); if (midiQueue.empty()) { midiLock.unlock(); break; } TAMidiMessage msg=midiQueue.front(); midiLock.unlock(); if (msg.type==TA_MIDI_SYSEX) { unsigned char* data=msg.sysExData.get(); for (size_t i=0; i=0 && learning<(int)midiMap.binds.size()) { midiMap.binds[learning].type=msg.type>>4; midiMap.binds[learning].channel=msg.type&15; midiMap.binds[learning].data1=msg.data[0]; switch (msg.type&0xf0) { case TA_MIDI_NOTE_OFF: case TA_MIDI_NOTE_ON: case TA_MIDI_AFTERTOUCH: case TA_MIDI_PITCH_BEND: case TA_MIDI_CONTROL: midiMap.binds[learning].data2=msg.data[1]; break; default: midiMap.binds[learning].data2=128; break; } } learning=-1; } else { int action=midiMap.at(msg); if (action!=0) { doAction(action); } else switch (msg.type&0xf0) { case TA_MIDI_NOTE_ON: if (midiMap.valueInputStyle==0 || midiMap.valueInputStyle>3 || cursor.xFine==0) { if (midiMap.noteInput && edit && msg.data[1]!=0) { noteInput( msg.data[0]-12, 0, midiMap.volInput?((int)(pow((double)msg.data[1]/127.0,midiMap.volExp)*127.0)):-1 ); } } else { if (edit && msg.data[1]!=0) { switch (midiMap.valueInputStyle) { case 1: { int val=msg.data[0]%24; if (val<16) { valueInput(val); } break; } case 2: valueInput(msg.data[0]&15); break; case 3: int val=altValues[msg.data[0]%24]; if (val>=0) { valueInput(val); } break; } } } break; case TA_MIDI_PROGRAM: if (midiMap.programChange) { curIns=msg.data[0]; if (curIns>=(int)e->song.ins.size()) curIns=e->song.ins.size()-1; wavePreviewInit=true; updateFMPreview=true; } break; case TA_MIDI_CONTROL: bool gchanged=false; if (msg.data[0]==midiMap.valueInputControlMSB) { midiMap.valueInputCurMSB=msg.data[1]; gchanged=true; } if (msg.data[0]==midiMap.valueInputControlLSB) { midiMap.valueInputCurLSB=msg.data[1]; gchanged=true; } if (msg.data[0]==midiMap.valueInputControlSingle) { midiMap.valueInputCurSingle=msg.data[1]; gchanged=true; } if (gchanged && cursor.xFine>0) { switch (midiMap.valueInputStyle) { case 4: // dual CC valueInput(((midiMap.valueInputCurMSB>>3)<<4)|(midiMap.valueInputCurLSB>>3),true); break; case 5: // 14-bit valueInput((midiMap.valueInputCurMSB<<1)|(midiMap.valueInputCurLSB>>6),true); break; case 6: // single CC valueInput((midiMap.valueInputCurSingle*255)/127,true); break; } } for (int i=0; i<18; i++) { bool changed=false; if (midiMap.valueInputSpecificStyle[i]!=0) { if (msg.data[0]==midiMap.valueInputSpecificMSB[i]) { changed=true; midiMap.valueInputCurMSBS[i]=msg.data[1]; } if (msg.data[0]==midiMap.valueInputSpecificLSB[i]) { changed=true; midiMap.valueInputCurLSBS[i]=msg.data[1]; } if (msg.data[0]==midiMap.valueInputSpecificSingle[i]) { changed=true; midiMap.valueInputCurSingleS[i]=msg.data[1]; } if (changed) switch (midiMap.valueInputStyle) { case 1: // dual CC valueInput(((midiMap.valueInputCurMSBS[i]>>3)<<4)|(midiMap.valueInputCurLSBS[i]>>3),true,i+2); break; case 2: // 14-bit valueInput((midiMap.valueInputCurMSBS[i]<<1)|(midiMap.valueInputCurLSBS[i]>>6),true,i+2); break; case 3: // single CC valueInput((midiMap.valueInputCurSingleS[i]*255)/127,true,i+2); break; } } } break; } } midiLock.lock(); midiQueue.pop(); midiLock.unlock(); } if (notifyWaveChange) { notifyWaveChange=false; e->notifyWaveChange(curWave); } eventTimeEnd=SDL_GetPerformanceCounter(); if (SDL_GetWindowFlags(sdlWin)&SDL_WINDOW_MINIMIZED) { SDL_Delay(30); drawHalt=0; continue; } if (firstFrame) { if (!tutorial.introPlayed || settings.alwaysPlayIntro==3 || (settings.alwaysPlayIntro==2 && curFileName.empty())) { unsigned char* introTemp=new unsigned char[intro_fur_len]; memcpy(introTemp,intro_fur,intro_fur_len); e->load(introTemp,intro_fur_len); } } if (!e->isRunning()) { activeNotes.clear(); memset(chanOscVol,0,DIV_MAX_CHANS*sizeof(float)); memset(chanOscPitch,0,DIV_MAX_CHANS*sizeof(float)); memset(chanOscBright,0,DIV_MAX_CHANS*sizeof(float)); e->synchronized([this]() { for (int i=0; igetTotalChannelCount(); i++) { DivDispatchOscBuffer* buf=e->getOscBuffer(i); if (buf!=NULL) { buf->needle=0; buf->readNeedle=0; } } }); } // recover from dead graphics if (rend->isDead() || killGraphics) { killGraphics=false; logW("graphics are dead! restarting..."); if (sampleTex!=NULL) { rend->destroyTexture(sampleTex); sampleTex=NULL; } if (chanOscGradTex!=NULL) { rend->destroyTexture(chanOscGradTex); chanOscGradTex=NULL; } for (auto& i: images) { if (i.second->tex!=NULL) { rend->destroyTexture(i.second->tex); i.second->tex=NULL; } } commitState(); rend->quitGUI(); rend->quit(); ImGui_ImplSDL2_Shutdown(); int initAttempts=0; SDL_Delay(500); logD("starting render backend..."); while (++initAttempts<=5) { if (rend->init(sdlWin)) { break; } SDL_Delay(1000); logV("trying again..."); } if (initAttempts>5) { reportError("can't keep going without graphics! Furnace will quit now."); quit=true; break; } rend->clear(ImVec4(0.0,0.0,0.0,1.0)); rend->present(); logD("preparing user interface..."); rend->initGUI(sdlWin); logD("building font..."); if (!ImGui::GetIO().Fonts->Build()) { logE("error while building font atlas!"); showError("error while loading fonts! please check your settings."); ImGui::GetIO().Fonts->Clear(); mainFont=ImGui::GetIO().Fonts->AddFontDefault(); patFont=mainFont; bigFont=mainFont; headFont=mainFont; if (rend) rend->destroyFontsTexture(); if (!ImGui::GetIO().Fonts->Build()) { logE("error again while building font atlas!"); } } firstFrame=true; mustClear=2; initialScreenWipe=1.0f; continue; } bool fontsFailed=false; layoutTimeBegin=SDL_GetPerformanceCounter(); if (!rend->newFrame()) { fontsFailed=true; } ImGui_ImplSDL2_NewFrame(sdlWin); ImGui::NewFrame(); curWindowLast=curWindow; curWindow=GUI_WINDOW_NOTHING; editOptsVisible=false; if (!mobileUI) { ImGui::BeginMainMenuBar(); if (ImGui::BeginMenu(settings.capitalMenuBar?"File":"file")) { if (ImGui::MenuItem("new...",BIND_FOR(GUI_ACTION_NEW))) { if (modified) { showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW); } else { displayNew=true; } } if (ImGui::MenuItem("open...",BIND_FOR(GUI_ACTION_OPEN))) { if (modified) { showWarning("Unsaved changes! Save changes before opening another file?",GUI_WARN_OPEN); } else { openFileDialog(GUI_FILE_OPEN); } } if (ImGui::BeginMenu("open recent")) { exitDisabledTimer=1; for (int i=0; i<(int)recentFile.size(); i++) { String item=recentFile[i]; if (ImGui::MenuItem(item.c_str())) { if (modified) { nextFile=item; showWarning("Unsaved changes! Save changes before opening file?",GUI_WARN_OPEN_DROP); } else { recentFile.erase(recentFile.begin()+i); i--; if (load(item)>0) { showError(fmt::sprintf("Error while loading file! (%s)",lastError)); } } } } if (recentFile.empty()) { ImGui::Text("nothing here yet"); } else { ImGui::Separator(); if (ImGui::MenuItem("clear history")) { showWarning("Are you sure you want to clear the recent file list?",GUI_WARN_CLEAR_HISTORY); } } ImGui::EndMenu(); } ImGui::Separator(); if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) { if (curFileName=="" || (curFileName.find(backupPath)==0) || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); } else { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } } } if (ImGui::MenuItem("save as...",BIND_FOR(GUI_ACTION_SAVE_AS))) { openFileDialog(GUI_FILE_SAVE); } if (ImGui::MenuItem("save as .dmf (1.1.3+)...")) { openFileDialog(GUI_FILE_SAVE_DMF); } if (ImGui::MenuItem("save as .dmf (1.0/legacy)...")) { openFileDialog(GUI_FILE_SAVE_DMF_LEGACY); } ImGui::Separator(); if (ImGui::BeginMenu("export audio...")) { exitDisabledTimer=1; if (ImGui::MenuItem("one file")) { openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); } if (ImGui::MenuItem("multiple files (one per chip)")) { openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); } if (ImGui::MenuItem("multiple files (one per channel)")) { openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL); } if (ImGui::InputInt("Loops",&exportLoops,1,2)) { if (exportLoops<0) exportLoops=0; } if (ImGui::InputDouble("Fade out (seconds)",&exportFadeOut,1.0,2.0,"%.1f")) { if (exportFadeOut<0.0) exportFadeOut=0.0; } ImGui::EndMenu(); } if (ImGui::BeginMenu("export VGM...")) { exitDisabledTimer=1; ImGui::Text("settings:"); if (ImGui::BeginCombo("format version",fmt::sprintf("%d.%.2x",vgmExportVersion>>8,vgmExportVersion&0xff).c_str())) { for (int i=0; i<7; i++) { if (ImGui::Selectable(fmt::sprintf("%d.%.2x",vgmVersions[i]>>8,vgmVersions[i]&0xff).c_str(),vgmExportVersion==vgmVersions[i])) { vgmExportVersion=vgmVersions[i]; } } ImGui::EndCombo(); } ImGui::Checkbox("loop",&vgmExportLoop); if (vgmExportLoop && e->song.loopModality==2) { ImGui::Text("loop trail:"); ImGui::Indent(); if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) { vgmExportTrailingTicks=-1; } if (ImGui::RadioButton("add one loop",vgmExportTrailingTicks==-2)) { vgmExportTrailingTicks=-2; } if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) { vgmExportTrailingTicks=0; } if (vgmExportTrailingTicks>=0) { ImGui::SameLine(); if (ImGui::InputInt("##TrailTicks",&vgmExportTrailingTicks,1,100)) { if (vgmExportTrailingTicks<0) vgmExportTrailingTicks=0; } } ImGui::Unindent(); } ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints); if (ImGui::IsItemHovered()) { ImGui::SetTooltip( "inserts data blocks on pattern changes.\n" "useful if you are writing a playback routine.\n\n" "the format of a pattern change data block is:\n" "67 66 FE ll ll ll ll 01 oo rr pp pp pp ...\n" "- ll: length, a 32-bit little-endian number\n" "- oo: order\n" "- rr: initial row (a 0Dxx effect is able to select a different row)\n" "- pp: pattern index (one per channel)\n\n" "pattern indexes are ordered as they appear in the song." ); } ImGui::Checkbox("direct stream mode",&vgmExportDirectStream); if (ImGui::IsItemHovered()) { ImGui::SetTooltip( "required for DualPCM and MSM6258 export.\n\n" "allows for volume/direction changes when playing samples,\n" "at the cost of a massive increase in file size." ); } ImGui::Text("chips to export:"); bool hasOneAtLeast=false; for (int i=0; isong.systemLen; i++) { int minVersion=e->minVGMVersion(e->song.system[i]); ImGui::BeginDisabled(minVersion>vgmExportVersion || minVersion==0); ImGui::Checkbox(fmt::sprintf("%d. %s##_SYSV%d",i+1,getSystemName(e->song.system[i]),i).c_str(),&willExport[i]); ImGui::EndDisabled(); if (minVersion>vgmExportVersion) { if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { ImGui::SetTooltip("this chip is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); } } else if (minVersion==0) { if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { ImGui::SetTooltip("this chip is not supported by the VGM format!"); } } else { if (willExport[i]) hasOneAtLeast=true; } } ImGui::Text("select the chip you wish to export,"); ImGui::Text("but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); if (hasOneAtLeast) { if (ImGui::MenuItem("click to export")) { openFileDialog(GUI_FILE_EXPORT_VGM); } } else { ImGui::Text("nothing to export"); } ImGui::EndMenu(); } int numZSMCompat=0; for (int i=0; isong.systemLen; i++) { if ((e->song.system[i] == DIV_SYSTEM_VERA) || (e->song.system[i] == DIV_SYSTEM_YM2151)) numZSMCompat++; } if (numZSMCompat > 0) { if (ImGui::BeginMenu("export ZSM...")) { exitDisabledTimer=1; ImGui::Text("Commander X16 Zsound Music File"); if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) { if (zsmExportTickRate<1) zsmExportTickRate=1; if (zsmExportTickRate>44100) zsmExportTickRate=44100; } ImGui::Checkbox("loop",&zsmExportLoop); ImGui::SameLine(); ImGui::Checkbox("optimize size",&zsmExportOptimize); ImGui::SameLine(); if (ImGui::Button("Begin Export")) { openFileDialog(GUI_FILE_EXPORT_ZSM); ImGui::CloseCurrentPopup(); } ImGui::EndMenu(); } } int numAmiga=0; for (int i=0; isong.systemLen; i++) { if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; } if (numAmiga && settings.iCannotWait) { if (ImGui::BeginMenu("export Amiga validation data...")) { exitDisabledTimer=1; ImGui::Text( "this is NOT ROM export! only use for making sure the\n" "Furnace Amiga emulator is working properly by\n" "comparing it with real Amiga output." ); ImGui::AlignTextToFramePadding(); ImGui::Text("Directory"); ImGui::SameLine(); ImGui::InputText("##AVDPath",&workingDirROMExport); if (ImGui::Button("Bake Data")) { std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); if (workingDirROMExport.size()>0) { if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; } for (DivROMExportOutput& i: out) { String path=workingDirROMExport+i.name; FILE* outFile=ps_fopen(path.c_str(),"wb"); if (outFile!=NULL) { fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); fclose(outFile); } i.data->finish(); delete i.data; } showError(fmt::sprintf("Done! Baked %d files.",(int)out.size())); ImGui::CloseCurrentPopup(); } ImGui::EndMenu(); } } if (ImGui::BeginMenu("export command stream...")) { exitDisabledTimer=1; ImGui::Text( "this option exports a text or binary file which\n" "contains a dump of the internal command stream\n" "produced when playing the song.\n\n" "technical/development use only!" ); if (ImGui::Button("export (binary)")) { openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); } if (ImGui::Button("export (text)")) { openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); } ImGui::EndMenu(); } ImGui::Separator(); if (!settings.classicChipOptions) { if (ImGui::MenuItem("manage chips")) { nextWindow=GUI_WINDOW_SYS_MANAGER; } } else { if (ImGui::BeginMenu("add chip...")) { exitDisabledTimer=1; DivSystem picked=systemPicker(); if (picked!=DIV_SYSTEM_NULL) { if (!e->addSystem(picked)) { showError("cannot add chip! ("+e->getLastError()+")"); } else { MARK_MODIFIED; } ImGui::CloseCurrentPopup(); if (e->song.autoSystem) { autoDetectSystem(); } updateWindowTitle(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("configure chip...")) { exitDisabledTimer=1; for (int i=0; isong.systemLen; i++) { if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSP%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { drawSysConf(i,e->song.system[i],e->song.systemFlags[i],true,true); ImGui::TreePop(); } } ImGui::EndMenu(); } if (ImGui::BeginMenu("change chip...")) { exitDisabledTimer=1; ImGui::Checkbox("Preserve channel positions",&preserveChanPos); for (int i=0; isong.systemLen; i++) { if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { DivSystem picked=systemPicker(); if (picked!=DIV_SYSTEM_NULL) { e->changeSystem(i,picked,preserveChanPos); MARK_MODIFIED; if (e->song.autoSystem) { autoDetectSystem(); } updateWindowTitle(); ImGui::CloseCurrentPopup(); } ImGui::EndMenu(); } } ImGui::EndMenu(); } if (ImGui::BeginMenu("remove chip...")) { exitDisabledTimer=1; ImGui::Checkbox("Preserve channel positions",&preserveChanPos); for (int i=0; isong.systemLen; i++) { if (ImGui::MenuItem(fmt::sprintf("%d. %s##_SYSR%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { if (!e->removeSystem(i,preserveChanPos)) { showError("cannot remove chip! ("+e->getLastError()+")"); } else { MARK_MODIFIED; } if (e->song.autoSystem) { autoDetectSystem(); updateWindowTitle(); } } } ImGui::EndMenu(); } } ImGui::BeginDisabled(exitDisabledTimer); ImGui::Separator(); if (ImGui::MenuItem("restore backup",BIND_FOR(GUI_ACTION_OPEN_BACKUP))) { doAction(GUI_ACTION_OPEN_BACKUP); } ImGui::Separator(); if (ImGui::MenuItem("exit")) { if (modified) { showWarning("Unsaved changes! Save before quitting?",GUI_WARN_QUIT); } else { quit=true; } } ImGui::EndDisabled(); ImGui::EndMenu(); } else { exitDisabledTimer=0; } if (ImGui::BeginMenu(settings.capitalMenuBar?"Edit":"edit")) { ImGui::Text("..."); ImGui::Separator(); if (ImGui::MenuItem("undo",BIND_FOR(GUI_ACTION_UNDO))) doUndo(); if (ImGui::MenuItem("redo",BIND_FOR(GUI_ACTION_REDO))) doRedo(); ImGui::Separator(); editOptions(true); ImGui::Separator(); if (ImGui::MenuItem("clear...")) { doAction(GUI_ACTION_CLEAR); } ImGui::EndMenu(); } if (ImGui::BeginMenu(settings.capitalMenuBar?"Settings":"settings")) { #ifndef IS_MOBILE if (ImGui::MenuItem("full screen",BIND_FOR(GUI_ACTION_FULLSCREEN),fullScreen)) { doAction(GUI_ACTION_FULLSCREEN); } #endif if (ImGui::MenuItem("lock layout",NULL,lockLayout)) { lockLayout=!lockLayout; } if (ImGui::MenuItem("basic mode",NULL,basicMode)) { if (basicMode) { showWarning("prepare to unlock the full power of Furnace!",GUI_WARN_BASIC_MODE); } else { showWarning("are you sure? this will disable several things.",GUI_WARN_BASIC_MODE); } } if (ImGui::MenuItem("visualizer",NULL,fancyPattern)) { fancyPattern=!fancyPattern; e->enableCommandStream(fancyPattern); e->getCommandStream(cmdStream); cmdStream.clear(); } if (ImGui::MenuItem("reset layout")) { showWarning("Are you sure you want to reset the workspace layout?",GUI_WARN_RESET_LAYOUT); } #ifdef IS_MOBILE if (ImGui::MenuItem("switch to mobile view")) { toggleMobileUI(!mobileUI); } #endif if (ImGui::MenuItem("settings...",BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) { syncSettings(); settingsOpen=true; } ImGui::EndMenu(); } if (ImGui::BeginMenu(settings.capitalMenuBar?"Window":"window")) { if (ImGui::MenuItem("song information",BIND_FOR(GUI_ACTION_WINDOW_SONG_INFO),songInfoOpen)) songInfoOpen=!songInfoOpen; if (ImGui::MenuItem("subsongs",BIND_FOR(GUI_ACTION_WINDOW_SUBSONGS),subSongsOpen)) subSongsOpen=!subSongsOpen; if (ImGui::MenuItem("speed",BIND_FOR(GUI_ACTION_WINDOW_SPEED),speedOpen)) speedOpen=!speedOpen; if (settings.unifiedDataView) { if (ImGui::MenuItem("assets",BIND_FOR(GUI_ACTION_WINDOW_INS_LIST),insListOpen)) insListOpen=!insListOpen; } else { if (ImGui::MenuItem("instruments",BIND_FOR(GUI_ACTION_WINDOW_INS_LIST),insListOpen)) insListOpen=!insListOpen; if (ImGui::MenuItem("wavetables",BIND_FOR(GUI_ACTION_WINDOW_WAVE_LIST),waveListOpen)) waveListOpen=!waveListOpen; if (ImGui::MenuItem("samples",BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_LIST),sampleListOpen)) sampleListOpen=!sampleListOpen; } if (ImGui::MenuItem("orders",BIND_FOR(GUI_ACTION_WINDOW_ORDERS),ordersOpen)) ordersOpen=!ordersOpen; if (ImGui::MenuItem("pattern",BIND_FOR(GUI_ACTION_WINDOW_PATTERN),patternOpen)) patternOpen=!patternOpen; if (ImGui::MenuItem("mixer",BIND_FOR(GUI_ACTION_WINDOW_MIXER),mixerOpen)) mixerOpen=!mixerOpen; if (!basicMode) { if (ImGui::MenuItem("grooves",BIND_FOR(GUI_ACTION_WINDOW_GROOVES),groovesOpen)) groovesOpen=!groovesOpen; if (ImGui::MenuItem("channels",BIND_FOR(GUI_ACTION_WINDOW_CHANNELS),channelsOpen)) channelsOpen=!channelsOpen; } if (ImGui::MenuItem("pattern manager",BIND_FOR(GUI_ACTION_WINDOW_PAT_MANAGER),patManagerOpen)) patManagerOpen=!patManagerOpen; if (!basicMode) { if (ImGui::MenuItem("chip manager",BIND_FOR(GUI_ACTION_WINDOW_SYS_MANAGER),sysManagerOpen)) sysManagerOpen=!sysManagerOpen; if (ImGui::MenuItem("compatibility flags",BIND_FOR(GUI_ACTION_WINDOW_COMPAT_FLAGS),compatFlagsOpen)) compatFlagsOpen=!compatFlagsOpen; } if (ImGui::MenuItem("song comments",BIND_FOR(GUI_ACTION_WINDOW_NOTES),notesOpen)) notesOpen=!notesOpen; ImGui::Separator(); if (ImGui::MenuItem("instrument editor",BIND_FOR(GUI_ACTION_WINDOW_INS_EDIT),insEditOpen)) insEditOpen=!insEditOpen; if (ImGui::MenuItem("wavetable editor",BIND_FOR(GUI_ACTION_WINDOW_WAVE_EDIT),waveEditOpen)) waveEditOpen=!waveEditOpen; if (ImGui::MenuItem("sample editor",BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_EDIT),sampleEditOpen)) sampleEditOpen=!sampleEditOpen; ImGui::Separator(); if (ImGui::MenuItem("play/edit controls",BIND_FOR(GUI_ACTION_WINDOW_EDIT_CONTROLS),editControlsOpen)) editControlsOpen=!editControlsOpen; if (ImGui::MenuItem("piano/input pad",BIND_FOR(GUI_ACTION_WINDOW_PIANO),pianoOpen)) pianoOpen=!pianoOpen; if (ImGui::MenuItem("oscilloscope (master)",BIND_FOR(GUI_ACTION_WINDOW_OSCILLOSCOPE),oscOpen)) oscOpen=!oscOpen; if (ImGui::MenuItem("oscilloscope (per-channel)",BIND_FOR(GUI_ACTION_WINDOW_CHAN_OSC),chanOscOpen)) chanOscOpen=!chanOscOpen; if (ImGui::MenuItem("volume meter",BIND_FOR(GUI_ACTION_WINDOW_VOL_METER),volMeterOpen)) volMeterOpen=!volMeterOpen; if (ImGui::MenuItem("clock",BIND_FOR(GUI_ACTION_WINDOW_CLOCK),clockOpen)) clockOpen=!clockOpen; if (ImGui::MenuItem("register view",BIND_FOR(GUI_ACTION_WINDOW_REGISTER_VIEW),regViewOpen)) regViewOpen=!regViewOpen; if (ImGui::MenuItem("log viewer",BIND_FOR(GUI_ACTION_WINDOW_LOG),logOpen)) logOpen=!logOpen; if (ImGui::MenuItem("statistics",BIND_FOR(GUI_ACTION_WINDOW_STATS),statsOpen)) statsOpen=!statsOpen; if (spoilerOpen) if (ImGui::MenuItem("spoiler",NULL,spoilerOpen)) spoilerOpen=!spoilerOpen; ImGui::EndMenu(); } if (ImGui::BeginMenu(settings.capitalMenuBar?"Help":"help")) { if (ImGui::MenuItem("effect list",BIND_FOR(GUI_ACTION_WINDOW_EFFECT_LIST),effectListOpen)) effectListOpen=!effectListOpen; if (ImGui::MenuItem("debug menu",BIND_FOR(GUI_ACTION_WINDOW_DEBUG))) debugOpen=!debugOpen; if (ImGui::MenuItem("inspector")) inspectorOpen=!inspectorOpen; if (ImGui::MenuItem("panic",BIND_FOR(GUI_ACTION_PANIC))) e->syncReset(); if (ImGui::MenuItem("about...",BIND_FOR(GUI_ACTION_WINDOW_ABOUT))) { aboutOpen=true; aboutScroll=0; } ImGui::EndMenu(); } ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PLAYBACK_STAT]); if (e->isPlaying()) { int totalTicks=e->getTotalTicks(); int totalSeconds=e->getTotalSeconds(); String info; DivGroovePattern gp=e->getSpeeds(); if (gp.len==2) { info=fmt::sprintf("| Speed %d:%d",gp.val[0],gp.val[1]); } else if (gp.len==1) { info=fmt::sprintf("| Speed %d",gp.val[0]); } else { info="| Groove"; } info+=fmt::sprintf(" @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getCurHz(),calcBPM(e->getSpeeds(),e->getCurHz(),e->curSubSong->virtualTempoN,e->curSubSong->virtualTempoD),e->getOrder(),e->curSubSong->ordersLen,e->getRow(),e->curSubSong->patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); ImGui::TextUnformatted(info.c_str()); } else { bool hasInfo=false; String info; if (cursor.xCoarse>=0 && cursor.xCoarsegetTotalChannelCount()) { DivPattern* p=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],false); if (cursor.xFine>=0) switch (cursor.xFine) { case 0: // note if (p->data[cursor.y][0]>0) { if (p->data[cursor.y][0]==100) { info=fmt::sprintf("Note off (cut)"); } else if (p->data[cursor.y][0]==101) { info=fmt::sprintf("Note off (release)"); } else if (p->data[cursor.y][0]==102) { info=fmt::sprintf("Macro release only"); } else { info=fmt::sprintf("Note on: %s",noteName(p->data[cursor.y][0],p->data[cursor.y][1])); } hasInfo=true; } break; case 1: // instrument if (p->data[cursor.y][2]>-1) { if (p->data[cursor.y][2]>=(int)e->song.ins.size()) { info=fmt::sprintf("Ins %d: ",p->data[cursor.y][2]); } else { DivInstrument* ins=e->getIns(p->data[cursor.y][2]); info=fmt::sprintf("Ins %d: %s",p->data[cursor.y][2],ins->name); } hasInfo=true; } break; case 2: // volume if (p->data[cursor.y][3]>-1) { int maxVol=e->getMaxVolumeChan(cursor.xCoarse); if (maxVol<1 || p->data[cursor.y][3]>maxVol) { info=fmt::sprintf("Set volume: %d (%.2X, INVALID!)",p->data[cursor.y][3],p->data[cursor.y][3]); } else { info=fmt::sprintf("Set volume: %d (%.2X, %d%%)",p->data[cursor.y][3],p->data[cursor.y][3],(p->data[cursor.y][3]*100)/maxVol); } hasInfo=true; } break; default: // effect int actualCursor=((cursor.xFine+1)&(~1)); if (p->data[cursor.y][actualCursor]>-1) { info=e->getEffectDesc(p->data[cursor.y][actualCursor],cursor.xCoarse,true); hasInfo=true; } break; } } if (hasInfo && (settings.statusDisplay==0 || settings.statusDisplay==2)) { ImGui::Text("| %s",info.c_str()); } else if (settings.statusDisplay==1 || settings.statusDisplay==2) { if (curFileName!="") ImGui::Text("| %s",curFileName.c_str()); } } ImGui::PopStyleColor(); if (modified) { ImGui::Text("| modified"); } ImGui::EndMainMenuBar(); } MEASURE(calcChanOsc,calcChanOsc()); if (followPattern) { curOrder=e->getOrder(); } if (mobileUI) { globalWinFlags=ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoBringToFrontOnFocus; //globalWinFlags=ImGuiWindowFlags_NoTitleBar; // scene handling goes here! MEASURE(mobileControls,drawMobileControls()); switch (mobScene) { case GUI_SCENE_ORDERS: ordersOpen=true; curWindow=GUI_WINDOW_ORDERS; MEASURE(orders,drawOrders()); break; case GUI_SCENE_INSTRUMENT: insEditOpen=true; curWindow=GUI_WINDOW_INS_EDIT; MEASURE(insEdit,drawInsEdit()); MEASURE(piano,drawPiano()); break; case GUI_SCENE_WAVETABLE: waveEditOpen=true; curWindow=GUI_WINDOW_WAVE_EDIT; MEASURE(waveEdit,drawWaveEdit()); MEASURE(piano,drawPiano()); break; case GUI_SCENE_SAMPLE: sampleEditOpen=true; curWindow=GUI_WINDOW_SAMPLE_EDIT; MEASURE(sampleEdit,drawSampleEdit()); MEASURE(piano,drawPiano()); break; case GUI_SCENE_CHANNELS: channelsOpen=true; curWindow=GUI_WINDOW_CHANNELS; MEASURE(channels,drawChannels()); break; case GUI_SCENE_CHIPS: sysManagerOpen=true; curWindow=GUI_WINDOW_SYS_MANAGER; MEASURE(sysManager,drawSysManager()); break; case GUI_SCENE_MIXER: mixerOpen=true; curWindow=GUI_WINDOW_MIXER; MEASURE(mixer,drawMixer()); break; default: patternOpen=true; curWindow=GUI_WINDOW_PATTERN; MEASURE(pattern,drawPattern()); MEASURE(piano,drawPiano()); MEASURE(mobileOrderSel,drawMobileOrderSel()); globalWinFlags=0; MEASURE(findReplace,drawFindReplace()); break; } globalWinFlags=0; MEASURE(settings,drawSettings()); MEASURE(debug,drawDebug()); MEASURE(log,drawLog()); MEASURE(compatFlags,drawCompatFlags()); MEASURE(stats,drawStats()); MEASURE(chanOsc,drawChanOsc()); MEASURE(regView,drawRegView()); } else { globalWinFlags=0; ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoWindowMenuButton|ImGuiDockNodeFlags_NoMove|ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0); MEASURE(subSongs,drawSubSongs()); MEASURE(findReplace,drawFindReplace()); MEASURE(spoiler,drawSpoiler()); MEASURE(pattern,drawPattern()); MEASURE(editControls,drawEditControls()); MEASURE(speed,drawSpeed()); if (!basicMode) { MEASURE(grooves,drawGrooves()); } MEASURE(songInfo,drawSongInfo()); MEASURE(orders,drawOrders()); MEASURE(sampleList,drawSampleList()); MEASURE(sampleEdit,drawSampleEdit()); MEASURE(waveList,drawWaveList()); MEASURE(waveEdit,drawWaveEdit()); MEASURE(insList,drawInsList()); MEASURE(insEdit,drawInsEdit()); MEASURE(mixer,drawMixer()); MEASURE(readOsc,readOsc()); MEASURE(osc,drawOsc()); MEASURE(chanOsc,drawChanOsc()); MEASURE(volMeter,drawVolMeter()); MEASURE(settings,drawSettings()); MEASURE(debug,drawDebug()); MEASURE(stats,drawStats()); if (!basicMode) { MEASURE(compatFlags,drawCompatFlags()); } MEASURE(piano,drawPiano()); MEASURE(notes,drawNotes()); if (!basicMode) { MEASURE(channels,drawChannels()); } MEASURE(patManager,drawPatManager()); if (!basicMode) { MEASURE(sysManager,drawSysManager()); } MEASURE(clock,drawClock()); MEASURE(regView,drawRegView()); MEASURE(log,drawLog()); MEASURE(effectList,drawEffectList()); } // release selection if mouse released if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) && selecting) { if (!selectingFull) cursor=selEnd; finishSelection(); if (!mobileUI) { demandScrollX=true; if (cursor.xCoarse==selStart.xCoarse && cursor.xFine==selStart.xFine && cursor.y==selStart.y && cursor.xCoarse==selEnd.xCoarse && cursor.xFine==selEnd.xFine && cursor.y==selEnd.y) { if (!settings.cursorMoveNoScroll) { updateScroll(cursor.y); } } } } for (int i=0; igetTotalChannelCount(); i++) { keyHit1[i]-=0.2f; if (keyHit1[i]<0.0f) keyHit1[i]=0.0f; } activateTutorial(GUI_TUTORIAL_OVERVIEW); if (inspectorOpen) ImGui::ShowMetricsWindow(&inspectorOpen); if (firstFrame) { firstFrame=false; #ifdef IS_MOBILE SDL_GetWindowSize(sdlWin,&scrW,&scrH); portrait=(scrWgetOutputSize(canvasW,canvasH); #endif if (patternOpen) nextWindow=GUI_WINDOW_PATTERN; #ifdef __APPLE__ SDL_RaiseWindow(sdlWin); #endif } #ifndef NFD_NON_THREADED if (fileDialog->isOpen() && settings.sysFileDialog) { ImGui::OpenPopup("System File Dialog Pending"); } if (ImGui::BeginPopupModal("System File Dialog Pending",NULL,ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoBackground|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove)) { if (!fileDialog->isOpen()) { ImGui::CloseCurrentPopup(); } ImDrawList* dl=ImGui::GetForegroundDrawList(); dl->AddRectFilled(ImVec2(0.0f,0.0f),ImVec2(canvasW,canvasH),ImGui::ColorConvertFloat4ToU32(uiColors[GUI_COLOR_MODAL_BACKDROP])); ImGui::EndPopup(); } #endif if (fileDialog->render(mobileUI?ImVec2(canvasW-(portrait?0:(60.0*dpiScale)),canvasH-60.0*dpiScale):ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(canvasW-((mobileUI && !portrait)?(60.0*dpiScale):0),canvasH-(mobileUI?(60.0*dpiScale):0)))) { bool openOpen=false; //ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NavEnableKeyboard; if ((curFileDialog==GUI_FILE_INS_OPEN || curFileDialog==GUI_FILE_INS_OPEN_REPLACE) && prevIns!=-3) { if (curFileDialog==GUI_FILE_INS_OPEN_REPLACE) { if (prevInsData!=NULL) { if (prevIns>=0 && prevIns<(int)e->song.ins.size()) { *e->song.ins[prevIns]=*prevInsData; } } } else { curIns=prevIns; wavePreviewInit=true; updateFMPreview=true; } prevIns=-3; } switch (curFileDialog) { case GUI_FILE_OPEN: case GUI_FILE_SAVE: case GUI_FILE_SAVE_DMF: case GUI_FILE_SAVE_DMF_LEGACY: workingDirSong=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_INS_OPEN: case GUI_FILE_INS_OPEN_REPLACE: case GUI_FILE_INS_SAVE: case GUI_FILE_INS_SAVE_DMP: workingDirIns=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_WAVE_OPEN: case GUI_FILE_WAVE_OPEN_REPLACE: case GUI_FILE_WAVE_SAVE: case GUI_FILE_WAVE_SAVE_DMW: case GUI_FILE_WAVE_SAVE_RAW: workingDirWave=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_SAMPLE_OPEN: case GUI_FILE_SAMPLE_OPEN_RAW: case GUI_FILE_SAMPLE_OPEN_REPLACE: case GUI_FILE_SAMPLE_OPEN_REPLACE_RAW: case GUI_FILE_SAMPLE_SAVE: case GUI_FILE_SAMPLE_SAVE_RAW: workingDirSample=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_EXPORT_AUDIO_ONE: case GUI_FILE_EXPORT_AUDIO_PER_SYS: case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL: workingDirAudioExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_EXPORT_VGM: workingDirVGMExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_EXPORT_ZSM: workingDirZSMExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_EXPORT_ROM: case GUI_FILE_EXPORT_CMDSTREAM: case GUI_FILE_EXPORT_CMDSTREAM_BINARY: workingDirROMExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_LOAD_MAIN_FONT: case GUI_FILE_LOAD_HEAD_FONT: case GUI_FILE_LOAD_PAT_FONT: workingDirFont=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_IMPORT_COLORS: case GUI_FILE_EXPORT_COLORS: workingDirColors=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_IMPORT_KEYBINDS: case GUI_FILE_EXPORT_KEYBINDS: workingDirKeybinds=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_IMPORT_LAYOUT: case GUI_FILE_EXPORT_LAYOUT: workingDirLayout=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_YRW801_ROM_OPEN: case GUI_FILE_TG100_ROM_OPEN: case GUI_FILE_MU5_ROM_OPEN: workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_CMDSTREAM_OPEN: workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_TEST_OPEN: case GUI_FILE_TEST_OPEN_MULTI: case GUI_FILE_TEST_SAVE: workingDirTest=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_OPEN_BACKUP: break; } if (fileDialog->isError()) { #if defined(_WIN32) || defined(__APPLE__) showError("there was an error in the file dialog! you may want to report this issue to:\nhttps://github.com/tildearrow/furnace/issues\ncheck the Log Viewer (window > log viewer) for more information.\n\nfor now please disable the system file picker in Settings > General."); #else #ifdef ANDROID showError("can't do anything without Storage permissions!"); #else showError("Zenity/KDialog not available!\nplease install one of these, or disable the system file picker in Settings > General."); #endif #endif } if (fileDialog->accepted()) { if (fileDialog->getFileName().empty()) { fileName=""; } else { fileName=fileDialog->getFileName()[0]; } if (fileName!="") { if (curFileDialog==GUI_FILE_SAVE) { checkExtension(".fur"); } if (curFileDialog==GUI_FILE_SAVE_DMF) { checkExtension(".dmf"); } if (curFileDialog==GUI_FILE_SAVE_DMF_LEGACY) { checkExtension(".dmf"); } if (curFileDialog==GUI_FILE_SAMPLE_SAVE || curFileDialog==GUI_FILE_EXPORT_AUDIO_ONE || curFileDialog==GUI_FILE_EXPORT_AUDIO_PER_SYS || curFileDialog==GUI_FILE_EXPORT_AUDIO_PER_CHANNEL) { checkExtension(".wav"); } if (curFileDialog==GUI_FILE_INS_SAVE) { checkExtension(".fui"); } if (curFileDialog==GUI_FILE_INS_SAVE_DMP) { checkExtension(".dmp"); } if (curFileDialog==GUI_FILE_WAVE_SAVE) { checkExtension(".fuw"); } if (curFileDialog==GUI_FILE_WAVE_SAVE_DMW) { checkExtension(".dmw"); } if (curFileDialog==GUI_FILE_WAVE_SAVE_RAW) { checkExtension(".raw"); } if (curFileDialog==GUI_FILE_EXPORT_VGM) { checkExtension(".vgm"); } if (curFileDialog==GUI_FILE_EXPORT_ZSM) { checkExtension(".zsm"); } if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM) { checkExtension(".txt"); } if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM_BINARY) { checkExtension(".bin"); } if (curFileDialog==GUI_FILE_EXPORT_COLORS) { checkExtension(".cfgc"); } if (curFileDialog==GUI_FILE_EXPORT_KEYBINDS) { checkExtension(".cfgk"); } if (curFileDialog==GUI_FILE_EXPORT_LAYOUT) { checkExtension(".ini"); } String copyOfName=fileName; switch (curFileDialog) { case GUI_FILE_OPEN: case GUI_FILE_OPEN_BACKUP: if (load(copyOfName)>0) { showError(fmt::sprintf("Error while loading file! (%s)",lastError)); } break; case GUI_FILE_SAVE: { bool saveWasSuccessful=true; if (save(copyOfName,0)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); saveWasSuccessful=false; } if (saveWasSuccessful && postWarnAction!=GUI_WARN_GENERIC) { switch (postWarnAction) { case GUI_WARN_QUIT: quit=true; break; case GUI_WARN_NEW: displayNew=true; break; case GUI_WARN_OPEN: openOpen=true; break; case GUI_WARN_OPEN_DROP: if (load(nextFile)>0) { showError(fmt::sprintf("Error while loading file! (%s)",lastError)); } nextFile=""; break; case GUI_WARN_OPEN_BACKUP: openFileDialog(GUI_FILE_OPEN_BACKUP); break; default: break; } postWarnAction=GUI_WARN_GENERIC; } else if (postWarnAction==GUI_WARN_OPEN_DROP) { nextFile=""; } break; } case GUI_FILE_SAVE_DMF: logD("saving: %s",copyOfName.c_str()); if (save(copyOfName,26)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } break; case GUI_FILE_SAVE_DMF_LEGACY: logD("saving: %s",copyOfName.c_str()); if (save(copyOfName,24)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } break; case GUI_FILE_INS_SAVE: if (curIns>=0 && curIns<(int)e->song.ins.size()) { if (e->song.ins[curIns]->save(copyOfName.c_str(),false,&e->song)) { pushRecentSys(copyOfName.c_str()); } } break; case GUI_FILE_INS_SAVE_DMP: if (curIns>=0 && curIns<(int)e->song.ins.size()) { if (!e->song.ins[curIns]->saveDMP(copyOfName.c_str())) { showError("error while saving instrument! make sure your instrument is compatible."); } else { pushRecentSys(copyOfName.c_str()); } } break; case GUI_FILE_WAVE_SAVE: if (curWave>=0 && curWave<(int)e->song.wave.size()) { if (e->song.wave[curWave]->save(copyOfName.c_str())) { pushRecentSys(copyOfName.c_str()); } } break; case GUI_FILE_WAVE_SAVE_DMW: if (curWave>=0 && curWave<(int)e->song.wave.size()) { if (e->song.wave[curWave]->saveDMW(copyOfName.c_str())) { pushRecentSys(copyOfName.c_str()); } } break; case GUI_FILE_WAVE_SAVE_RAW: if (curWave>=0 && curWave<(int)e->song.wave.size()) { if (e->song.wave[curWave]->saveRaw(copyOfName.c_str())) { pushRecentSys(copyOfName.c_str()); } } break; case GUI_FILE_SAMPLE_OPEN: { String errs="there were some errors while loading wavetables:\n"; bool warn=false; for (String i: fileDialog->getFileName()) { DivSample* s=e->sampleFromFile(i.c_str()); if (s==NULL) { if (fileDialog->getFileName().size()>1) { warn=true; errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError()); } else { showError(e->getLastError()); } } else { if (e->addSamplePtr(s)==-1) { if (fileDialog->getFileName().size()>1) { warn=true; errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError()); } else { showError(e->getLastError()); } } else { MARK_MODIFIED; } } } if (warn) { showWarning(errs,GUI_WARN_GENERIC); } break; } case GUI_FILE_SAMPLE_OPEN_REPLACE: { DivSample* s=e->sampleFromFile(copyOfName.c_str()); if (s==NULL) { showError(e->getLastError()); } else { if (curSample>=0 && curSample<(int)e->song.sample.size()) { e->lockEngine([this,s]() { // if it crashes here please tell me... DivSample* oldSample=e->song.sample[curSample]; e->song.sample[curSample]=s; delete oldSample; e->renderSamples(); MARK_MODIFIED; }); } else { showError("...but you haven't selected a sample!"); delete s; } } break; } case GUI_FILE_SAMPLE_OPEN_RAW: case GUI_FILE_SAMPLE_OPEN_REPLACE_RAW: pendingRawSample=copyOfName; pendingRawSampleReplace=(curFileDialog==GUI_FILE_SAMPLE_OPEN_REPLACE_RAW); displayPendingRawSample=true; break; case GUI_FILE_SAMPLE_SAVE: if (curSample>=0 && curSample<(int)e->song.sample.size()) { if (!e->song.sample[curSample]->save(copyOfName.c_str())) { showError("could not save sample! open Log Viewer for more information."); } else { pushRecentSys(copyOfName.c_str()); } } break; case GUI_FILE_SAMPLE_SAVE_RAW: if (curSample>=0 && curSample<(int)e->song.sample.size()) { if (!e->song.sample[curSample]->saveRaw(copyOfName.c_str())) { showError("could not save sample! open Log Viewer for more information."); } else { pushRecentSys(copyOfName.c_str()); } } break; case GUI_FILE_EXPORT_AUDIO_ONE: exportAudio(copyOfName,DIV_EXPORT_MODE_ONE); break; case GUI_FILE_EXPORT_AUDIO_PER_SYS: exportAudio(copyOfName,DIV_EXPORT_MODE_MANY_SYS); break; case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL: exportAudio(copyOfName,DIV_EXPORT_MODE_MANY_CHAN); break; case GUI_FILE_INS_OPEN: { std::vector instruments; bool ask=false; bool warn=false; String warns="there were some warnings/errors while loading instruments:\n"; int sampleCountBefore=e->song.sampleLen; for (String i: fileDialog->getFileName()) { std::vector insTemp=e->instrumentFromFile(i.c_str()); if (insTemp.empty()) { warn=true; warns+=fmt::sprintf("> %s: cannot load instrument! (%s)\n",i,e->getLastError()); } else if (!e->getWarnings().empty()) { warn=true; warns+=fmt::sprintf("> %s:\n%s\n",i,e->getWarnings()); } if (insTemp.size()>1) ask=true; for (DivInstrument* j: insTemp) { instruments.push_back(j); } } if (e->song.sampleLen!=sampleCountBefore) { e->renderSamplesP(); } if (warn) { if (instruments.empty()) { if (fileDialog->getFileName().size()>1) { showError(warns); } else { showError("cannot load instrument! ("+e->getLastError()+")"); } } else { showWarning(warns,GUI_WARN_GENERIC); } } else if (instruments.empty()) { showError("congratulations! you managed to load nothing.\nyou are entitled to a bug report."); } if (!instruments.empty()) { if (ask) { // ask which instruments to load for (DivInstrument* i: instruments) { pendingIns.push_back(std::make_pair(i,false)); } displayPendingIns=true; pendingInsSingle=false; } else { // load the only instrument for (DivInstrument* i: instruments) { e->addInstrumentPtr(i); } } } break; } case GUI_FILE_INS_OPEN_REPLACE: { int sampleCountBefore=e->song.sampleLen; std::vector instruments=e->instrumentFromFile(copyOfName.c_str()); if (!instruments.empty()) { if (e->song.sampleLen!=sampleCountBefore) { e->renderSamplesP(); } if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } if (instruments.size()>1) { // ask which instrument for (DivInstrument* i: instruments) { pendingIns.push_back(std::make_pair(i,false)); } displayPendingIns=true; pendingInsSingle=true; } else { // replace with the only instrument if (curIns>=0 && curIns<(int)e->song.ins.size()) { *e->song.ins[curIns]=*instruments[0]; } else { showError("...but you haven't selected an instrument!"); } for (DivInstrument* i: instruments) { delete i; } } } else { showError("cannot load instrument! ("+e->getLastError()+")"); } break; } case GUI_FILE_WAVE_OPEN: { String errs="there were some errors while loading wavetables:\n"; bool warn=false; for (String i: fileDialog->getFileName()) { DivWavetable* wave=e->waveFromFile(i.c_str()); if (wave==NULL) { if (fileDialog->getFileName().size()>1) { warn=true; errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError()); } else { showError("cannot load wavetable! ("+e->getLastError()+")"); } } else { if (e->addWavePtr(wave)==-1) { if (fileDialog->getFileName().size()>1) { warn=true; errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError()); } else { showError("cannot load wavetable! ("+e->getLastError()+")"); } } else { MARK_MODIFIED; RESET_WAVE_MACRO_ZOOM; } } } if (warn) { showWarning(errs,GUI_WARN_GENERIC); } break; } case GUI_FILE_WAVE_OPEN_REPLACE: { DivWavetable* wave=e->waveFromFile(copyOfName.c_str()); if (wave==NULL) { showError("cannot load wavetable! ("+e->getLastError()+")"); } else { if (curWave>=0 && curWave<(int)e->song.wave.size()) { e->lockEngine([this,wave]() { *e->song.wave[curWave]=*wave; MARK_MODIFIED; }); } else { showError("...but you haven't selected a wavetable!"); } delete wave; } break; } case GUI_FILE_EXPORT_VGM: { SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion,vgmExportPatternHints,vgmExportDirectStream,vgmExportTrailingTicks); if (w!=NULL) { FILE* f=ps_fopen(copyOfName.c_str(),"wb"); if (f!=NULL) { fwrite(w->getFinalBuf(),1,w->size(),f); fclose(f); pushRecentSys(copyOfName.c_str()); } else { showError("could not open file!"); } w->finish(); delete w; if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } } else { showError(fmt::sprintf("could not write VGM! (%s)",e->getLastError())); } break; } case GUI_FILE_EXPORT_ZSM: { SafeWriter* w=e->saveZSM(zsmExportTickRate,zsmExportLoop,zsmExportOptimize); if (w!=NULL) { FILE* f=ps_fopen(copyOfName.c_str(),"wb"); if (f!=NULL) { fwrite(w->getFinalBuf(),1,w->size(),f); fclose(f); pushRecentSys(copyOfName.c_str()); } else { showError("could not open file!"); } w->finish(); delete w; if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } } else { showError(fmt::sprintf("Could not write ZSM! (%s)",e->getLastError())); } break; } case GUI_FILE_EXPORT_ROM: showError("Coming soon!"); break; case GUI_FILE_EXPORT_CMDSTREAM: case GUI_FILE_EXPORT_CMDSTREAM_BINARY: { bool isBinary=(curFileDialog==GUI_FILE_EXPORT_CMDSTREAM_BINARY); SafeWriter* w=e->saveCommand(isBinary); if (w!=NULL) { FILE* f=ps_fopen(copyOfName.c_str(),"wb"); if (f!=NULL) { fwrite(w->getFinalBuf(),1,w->size(),f); fclose(f); pushRecentSys(copyOfName.c_str()); } else { showError("could not open file!"); } w->finish(); delete w; if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } } else { showError(fmt::sprintf("could not write command stream! (%s)",e->getLastError())); } break; } case GUI_FILE_LOAD_MAIN_FONT: settings.mainFontPath=copyOfName; break; case GUI_FILE_LOAD_HEAD_FONT: settings.headFontPath=copyOfName; break; case GUI_FILE_LOAD_PAT_FONT: settings.patFontPath=copyOfName; break; case GUI_FILE_IMPORT_COLORS: importColors(copyOfName); break; case GUI_FILE_IMPORT_KEYBINDS: importKeybinds(copyOfName); break; case GUI_FILE_IMPORT_LAYOUT: importLayout(copyOfName); break; case GUI_FILE_EXPORT_COLORS: exportColors(copyOfName); break; case GUI_FILE_EXPORT_KEYBINDS: exportKeybinds(copyOfName); break; case GUI_FILE_EXPORT_LAYOUT: exportLayout(copyOfName); break; case GUI_FILE_YRW801_ROM_OPEN: settings.yrw801Path=copyOfName; break; case GUI_FILE_TG100_ROM_OPEN: settings.tg100Path=copyOfName; break; case GUI_FILE_MU5_ROM_OPEN: settings.mu5Path=copyOfName; break; case GUI_FILE_CMDSTREAM_OPEN: if (loadStream(copyOfName)>0) { showError(fmt::sprintf("Error while loading file! (%s)",lastError)); } break; case GUI_FILE_TEST_OPEN: showWarning(fmt::sprintf("You opened: %s",copyOfName),GUI_WARN_GENERIC); break; case GUI_FILE_TEST_OPEN_MULTI: { String msg="You opened:"; for (String i: fileDialog->getFileName()) { msg+=fmt::sprintf("\n- %s",i); } showWarning(msg,GUI_WARN_GENERIC); break; } case GUI_FILE_TEST_SAVE: showWarning(fmt::sprintf("You saved: %s",copyOfName),GUI_WARN_GENERIC); break; } curFileDialog=GUI_FILE_OPEN; } } fileDialog->close(); postWarnAction=GUI_WARN_GENERIC; if (openOpen) { openFileDialog(GUI_FILE_OPEN); } } if (warnQuit && introPos>=11.0) { warnQuit=false; ImGui::OpenPopup("Warning"); } if (displayError && introPos>=11.0) { displayError=false; ImGui::OpenPopup("Error"); } if (displayPendingIns) { displayPendingIns=false; ImGui::OpenPopup("Select Instrument"); } if (displayPendingRawSample) { displayPendingRawSample=false; ImGui::OpenPopup("Import Raw Sample"); } if (displayInsTypeList) { displayInsTypeList=false; ImGui::OpenPopup("InsTypeList"); } if (displayExporting) { displayExporting=false; ImGui::OpenPopup("Rendering..."); } if (displayNew) { newSongQuery=""; newSongFirstFrame=true; displayNew=false; if (settings.newSongBehavior==1) { e->createNewFromDefaults(); undoHist.clear(); redoHist.clear(); curFileName=""; modified=false; curNibble=false; orderNibble=false; orderCursor=-1; samplePos=0; updateSampleTex=true; selStart=SelectionPoint(); selEnd=SelectionPoint(); cursor=SelectionPoint(); updateWindowTitle(); } else { ImGui::OpenPopup("New Song"); } } if (displayEditString) { ImGui::OpenPopup("EditString"); } if (nextWindow==GUI_WINDOW_ABOUT) { aboutOpen=true; nextWindow=GUI_WINDOW_NOTHING; } if (aboutOpen) drawAbout(); MEASURE_BEGIN(popup); centerNextWindow("Rendering...",canvasW,canvasH); if (ImGui::BeginPopupModal("Rendering...",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Please wait..."); if (ImGui::Button("Abort")) { if (e->haltAudioFile()) { ImGui::CloseCurrentPopup(); } } if (!e->isExporting()) { e->finishAudioFile(); ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } drawTutorial(); ImVec2 newSongMinSize=mobileUI?ImVec2(canvasW-(portrait?0:(60.0*dpiScale)),canvasH-60.0*dpiScale):ImVec2(400.0f*dpiScale,200.0f*dpiScale); ImVec2 newSongMaxSize=ImVec2(canvasW-((mobileUI && !portrait)?(60.0*dpiScale):0),canvasH-(mobileUI?(60.0*dpiScale):0)); ImGui::SetNextWindowSizeConstraints(newSongMinSize,newSongMaxSize); if (ImGui::BeginPopupModal("New Song",NULL,ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) { ImGui::SetWindowPos(ImVec2(((canvasW)-ImGui::GetWindowSize().x)*0.5,((canvasH)-ImGui::GetWindowSize().y)*0.5)); if (ImGui::GetWindowSize().xsong.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); postWarnAction=GUI_WARN_QUIT; } else { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } else { quit=true; } } } ImGui::SameLine(); if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); quit=true; } ImGui::SameLine(); if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_NEW: if (ImGui::Button("Yes")) { ImGui::CloseCurrentPopup(); if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); postWarnAction=GUI_WARN_NEW; } else { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } else { displayNew=true; } } } ImGui::SameLine(); if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); displayNew=true; } ImGui::SameLine(); if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_OPEN: if (ImGui::Button("Yes")) { ImGui::CloseCurrentPopup(); if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); postWarnAction=GUI_WARN_OPEN; } else { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } else { openFileDialog(GUI_FILE_OPEN); } } } ImGui::SameLine(); if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); openFileDialog(GUI_FILE_OPEN); } ImGui::SameLine(); if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_OPEN_BACKUP: if (ImGui::Button("Yes")) { ImGui::CloseCurrentPopup(); if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); postWarnAction=GUI_WARN_OPEN_BACKUP; } else { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } else { openFileDialog(GUI_FILE_OPEN_BACKUP); } } } ImGui::SameLine(); if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); openFileDialog(GUI_FILE_OPEN_BACKUP); } ImGui::SameLine(); if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_OPEN_DROP: if (ImGui::Button("Yes")) { ImGui::CloseCurrentPopup(); if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); postWarnAction=GUI_WARN_OPEN_DROP; } else { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); nextFile=""; } else { if (load(nextFile)>0) { showError(fmt::sprintf("Error while loading file! (%s)",lastError)); } nextFile=""; } } } ImGui::SameLine(); if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); if (load(nextFile)>0) { showError(fmt::sprintf("Error while loading file! (%s)",lastError)); } nextFile=""; } ImGui::SameLine(); if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); nextFile=""; } break; case GUI_WARN_RESET_LAYOUT: if (ImGui::Button("Yes")) { ImGui::CloseCurrentPopup(); if (!mobileUI) { ImGui::LoadIniSettingsFromMemory(defaultLayout); if (!ImGui::SaveIniSettingsToDisk(finalLayoutPath,true)) { reportError(fmt::sprintf("could NOT save layout! %s",strerror(errno))); } } } ImGui::SameLine(); if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_RESET_KEYBINDS: if (ImGui::Button("Yes")) { ImGui::CloseCurrentPopup(); resetKeybinds(); } ImGui::SameLine(); if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_RESET_COLORS: if (ImGui::Button("Yes")) { ImGui::CloseCurrentPopup(); resetColors(); applyUISettings(false); } ImGui::SameLine(); if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_CLOSE_SETTINGS: if (ImGui::Button("Yes")) { ImGui::CloseCurrentPopup(); settingsOpen=false; willCommit=true; } ImGui::SameLine(); if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); settingsOpen=false; syncSettings(); } ImGui::SameLine(); if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_CLEAR: if (ImGui::Button("All subsongs")) { stop(); e->clearSubSongs(); curOrder=0; oldOrder=0; oldOrder1=0; MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Current subsong")) { stop(); e->lockEngine([this]() { e->curSubSong->clearData(); }); e->setOrder(0); curOrder=0; oldOrder=0; oldOrder1=0; MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Orders")) { stop(); e->lockEngine([this]() { memset(e->curOrders->ord,0,DIV_MAX_CHANS*DIV_MAX_PATTERNS); e->curSubSong->ordersLen=1; }); e->setOrder(0); curOrder=0; oldOrder=0; oldOrder1=0; MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Pattern")) { stop(); e->lockEngine([this]() { for (int i=0; igetTotalChannelCount(); i++) { DivPattern* pat=e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],true); memset(pat->data,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short)); for (int j=0; jdata[j][0]=0; pat->data[j][1]=0; } } }); MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Instruments")) { stop(); e->lockEngine([this]() { e->song.clearInstruments(); }); curIns=-1; MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Wavetables")) { stop(); e->lockEngine([this]() { e->song.clearWavetables(); }); curWave=0; MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Samples")) { stop(); e->lockEngine([this]() { e->song.clearSamples(); }); curSample=0; ImGui::CloseCurrentPopup(); } if (ImGui::Button("Wait! What am I doing? Cancel!") || ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_SUBSONG_DEL: if (ImGui::Button("Yes")) { if (e->removeSubSong(e->getCurrentSubSong())) { undoHist.clear(); redoHist.clear(); updateScroll(0); oldOrder=0; oldOrder1=0; oldRow=0; cursor.xCoarse=0; cursor.xFine=0; cursor.y=0; selStart=cursor; selEnd=cursor; curOrder=0; MARK_MODIFIED; } ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_SYSTEM_DEL: if (ImGui::Button("Yes")) { e->removeSystem(sysToDelete,preserveChanPos); if (e->song.autoSystem) { autoDetectSystem(); updateWindowTitle(); MARK_MODIFIED; } ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_CLEAR_HISTORY: if (ImGui::Button("Yes")) { recentFile.clear(); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_BASIC_MODE: if (ImGui::Button("Yes")) { basicMode=!basicMode; ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_GENERIC: if (ImGui::Button("OK")) { ImGui::CloseCurrentPopup(); } break; } ImGui::EndPopup(); } if (ImGui::BeginPopup("InsTypeList",ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) { char temp[1024]; for (DivInstrumentType& i: makeInsTypeList) { strncpy(temp,insTypes[i][0],1023); if (ImGui::MenuItem(temp)) { // create ins curIns=e->addInstrument(-1,i); if (curIns==-1) { showError("too many instruments!"); } else { if (displayInsTypeListMakeInsSample>=0 && displayInsTypeListMakeInsSample<(int)e->song.sample.size()) { e->song.ins[curIns]->type=i; e->song.ins[curIns]->name=e->song.sample[displayInsTypeListMakeInsSample]->name; e->song.ins[curIns]->amiga.initSample=displayInsTypeListMakeInsSample; if (i!=DIV_INS_AMIGA) e->song.ins[curIns]->amiga.useSample=true; nextWindow=GUI_WINDOW_INS_EDIT; wavePreviewInit=true; updateFMPreview=true; } if (settings.blankIns) { e->song.ins[curIns]->fm.fb=0; for (int i=0; i<4; i++) { e->song.ins[curIns]->fm.op[i]=DivInstrumentFM::Operator(); e->song.ins[curIns]->fm.op[i].ar=31; e->song.ins[curIns]->fm.op[i].dr=31; e->song.ins[curIns]->fm.op[i].rr=15; e->song.ins[curIns]->fm.op[i].tl=127; e->song.ins[curIns]->fm.op[i].dt=3; } } MARK_MODIFIED; } } } ImGui::EndPopup(); } // TODO: // - multiple selection // - replace instrument centerNextWindow("Select Instrument",canvasW,canvasH); if (ImGui::BeginPopupModal("Select Instrument",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { bool quitPlease=false; if (pendingInsSingle) { ImGui::Text("this is an instrument bank! select which one to use:"); } else { ImGui::AlignTextToFramePadding(); ImGui::Text("this is an instrument bank! select which ones to load:"); ImGui::SameLine(); if (ImGui::Button("All")) { for (std::pair& i: pendingIns) { i.second=true; } } ImGui::SameLine(); if (ImGui::Button("None")) { for (std::pair& i: pendingIns) { i.second=false; } } } bool anySelected=false; float sizeY=ImGui::GetFrameHeightWithSpacing()*pendingIns.size(); if (sizeY>(canvasH-180.0*dpiScale)) { sizeY=canvasH-180.0*dpiScale; if (sizeY<60.0*dpiScale) sizeY=60.0*dpiScale; } if (ImGui::BeginTable("PendingInsList",1,ImGuiTableFlags_ScrollY,ImVec2(0.0f,sizeY))) { for (size_t i=0; iname); if (pendingInsSingle) { if (ImGui::Selectable(id.c_str())) { pendingIns[i].second=true; quitPlease=true; } } else { ImGui::Checkbox(id.c_str(),&pendingIns[i].second); } if (pendingIns[i].second) anySelected=true; } ImGui::EndTable(); } if (!pendingInsSingle) { ImGui::BeginDisabled(!anySelected); if (ImGui::Button("OK")) { quitPlease=true; } ImGui::EndDisabled(); ImGui::SameLine(); } if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) { for (std::pair& i: pendingIns) { i.second=false; } quitPlease=true; } if (quitPlease) { ImGui::CloseCurrentPopup(); for (std::pair& i: pendingIns) { if (!i.second || pendingInsSingle) { if (i.second) { if (curIns>=0 && curIns<(int)e->song.ins.size()) { *e->song.ins[curIns]=*i.first; } else { showError("...but you haven't selected an instrument!"); } } delete i.first; } else { e->addInstrumentPtr(i.first); } } pendingIns.clear(); } ImGui::EndPopup(); } centerNextWindow("Import Raw Sample",canvasW,canvasH); if (ImGui::BeginPopupModal("Import Raw Sample",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Data type:"); for (int i=0; isampleFromFileRaw(pendingRawSample.c_str(),(DivSampleDepth)pendingRawSampleDepth,pendingRawSampleChannels,pendingRawSampleBigEndian,pendingRawSampleUnsigned,pendingRawSampleSwapNibbles); if (s==NULL) { showError(e->getLastError()); } else { if (pendingRawSampleReplace) { if (curSample>=0 && curSample<(int)e->song.sample.size()) { e->lockEngine([this,s]() { // if it crashes here please tell me... DivSample* oldSample=e->song.sample[curSample]; e->song.sample[curSample]=s; delete oldSample; e->renderSamples(); MARK_MODIFIED; }); } else { showError("...but you haven't selected a sample!"); delete s; } } else { if (e->addSamplePtr(s)==-1) { showError(e->getLastError()); } else { MARK_MODIFIED; } } } ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Cancel") || ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } if (ImGui::BeginPopup("EditString",ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) { if (editString==NULL) { ImGui::Text("Error! No string provided!"); } else { if (displayEditString) { ImGui::SetItemDefaultFocus(); ImGui::SetKeyboardFocusHere(); } ImGui::InputText("##StringVal",editString); } displayEditString=false; ImGui::SameLine(); if (ImGui::Button("OK") || ImGui::IsKeyPressed(ImGuiKey_Enter,false)) { editString=NULL; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } else { editString=NULL; } MEASURE_END(popup); if (!tutorial.introPlayed || settings.alwaysPlayIntro!=0) { MEASURE_BEGIN(intro); initialScreenWipe=0; if (settings.alwaysPlayIntro==1) { shortIntro=true; } drawIntro(introPos); MEASURE_END(intro); } else { introPos=12.0; } #ifdef DIV_UNSTABLE { ImDrawList* dl=ImGui::GetForegroundDrawList(); ImVec2 markPos=ImVec2(canvasW-ImGui::CalcTextSize(DIV_VERSION).x-6.0*dpiScale,4.0*dpiScale); ImVec4 markColor=uiColors[GUI_COLOR_TEXT]; markColor.w=0.67f; dl->AddText(markPos,ImGui::ColorConvertFloat4ToU32(markColor),DIV_VERSION); } #endif layoutTimeEnd=SDL_GetPerformanceCounter(); // backup trigger if (modified) { if (backupTimer>0) { backupTimer=(backupTimer-ImGui::GetIO().DeltaTime); if (backupTimer<=0) { backupTask=std::async(std::launch::async,[this]() -> bool { backupLock.lock(); logV("backupPath: %s",backupPath); logV("curFileName: %s",curFileName); if (curFileName.find(backupPath)==0) { logD("backup file open. not saving backup."); backupTimer=30.0; backupLock.unlock(); return true; } if (!dirExists(backupPath.c_str())) { if (!makeDir(backupPath.c_str())) { logW("could not create backup directory!"); backupTimer=30.0; backupLock.unlock(); return false; } } logD("saving backup..."); SafeWriter* w=e->saveFur(true,true); logV("writing file..."); if (w!=NULL) { size_t sepPos=curFileName.rfind(DIR_SEPARATOR); String backupPreBaseName; String backupBaseName; String backupFileName; if (sepPos==String::npos) { backupPreBaseName=curFileName; } else { backupPreBaseName=curFileName.substr(sepPos+1); } size_t dotPos=backupPreBaseName.rfind('.'); if (dotPos!=String::npos) { backupPreBaseName=backupPreBaseName.substr(0,dotPos); } for (char i: backupPreBaseName) { if (backupBaseName.size()>=48) break; if ((i>='0' && i<='9') || (i>='A' && i<='Z') || (i>='a' && i<='z') || i=='_' || i=='-' || i==' ') backupBaseName+=i; } if (backupBaseName.empty()) backupBaseName="untitled"; backupFileName=backupBaseName; time_t curTime=time(NULL); struct tm curTM; #ifdef _WIN32 struct tm* tempTM=localtime(&curTime); if (tempTM==NULL) { backupFileName+="-unknownTime.fur"; } else { curTM=*tempTM; backupFileName+=fmt::sprintf("-%d%.2d%.2d-%.2d%.2d%.2d.fur",curTM.tm_year+1900,curTM.tm_mon+1,curTM.tm_mday,curTM.tm_hour,curTM.tm_min,curTM.tm_sec); } #else if (localtime_r(&curTime,&curTM)==NULL) { backupFileName+="-unknownTime.fur"; } else { backupFileName+=fmt::sprintf("-%d%.2d%.2d-%.2d%.2d%.2d.fur",curTM.tm_year+1900,curTM.tm_mon+1,curTM.tm_mday,curTM.tm_hour,curTM.tm_min,curTM.tm_sec); } #endif String finalPath=backupPath+String(DIR_SEPARATOR_STR)+backupFileName; FILE* outFile=ps_fopen(finalPath.c_str(),"wb"); if (outFile!=NULL) { if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { logW("did not write backup entirely: %s!",strerror(errno)); } fclose(outFile); } else { logW("could not save backup: %s!",strerror(errno)); } w->finish(); // delete previous backup if there are too many delFirstBackup(backupBaseName); } logD("backup saved."); backupTimer=30.0; backupLock.unlock(); return true; }); } } } sampleMapWaitingInput=(curWindow==GUI_WINDOW_INS_EDIT && sampleMapFocused); curWindowThreadSafe=curWindow; if (curWindow!=curWindowLast) { int curWindowCat=0; int lastWindowCat=0; switch (curWindow) { case GUI_WINDOW_WAVE_LIST: case GUI_WINDOW_WAVE_EDIT: curWindowCat=1; break; case GUI_WINDOW_SAMPLE_LIST: case GUI_WINDOW_SAMPLE_EDIT: curWindowCat=2; break; default: curWindowCat=0; break; } switch (curWindowLast) { case GUI_WINDOW_WAVE_LIST: case GUI_WINDOW_WAVE_EDIT: lastWindowCat=1; break; case GUI_WINDOW_SAMPLE_LIST: case GUI_WINDOW_SAMPLE_EDIT: lastWindowCat=2; break; default: lastWindowCat=0; break; } if (curWindowCat!=lastWindowCat) { switch (lastWindowCat) { case 0: e->autoNoteOffAll(); break; case 1: e->stopWavePreview(); break; case 2: e->stopSamplePreview(); break; } } } if (!settings.renderClearPos) { rend->clear(uiColors[GUI_COLOR_BACKGROUND]); } renderTimeBegin=SDL_GetPerformanceCounter(); ImGui::Render(); renderTimeEnd=SDL_GetPerformanceCounter(); drawTimeBegin=SDL_GetPerformanceCounter(); rend->renderGUI(); if (mustClear) { rend->clear(ImVec4(0,0,0,0)); mustClear--; } else { if (initialScreenWipe>0.0f && !settings.disableFadeIn) { WAKE_UP; initialScreenWipe-=ImGui::GetIO().DeltaTime*5.0f; if (initialScreenWipe>0.0f) { rend->wipe(pow(initialScreenWipe,2.0f)); } } } drawTimeEnd=SDL_GetPerformanceCounter(); rend->present(); if (settings.renderClearPos) { rend->clear(uiColors[GUI_COLOR_BACKGROUND]); } layoutTimeDelta=layoutTimeEnd-layoutTimeBegin; renderTimeDelta=renderTimeEnd-renderTimeBegin; drawTimeDelta=drawTimeEnd-drawTimeBegin; eventTimeDelta=eventTimeEnd-eventTimeBegin; soloTimeout-=ImGui::GetIO().DeltaTime; if (soloTimeout<0) { soloTimeout=0; } else { WAKE_UP; } if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) { exitDisabledTimer=0; } wheelX=0; wheelY=0; wantScrollList=false; pressedPoints.clear(); releasedPoints.clear(); if (willCommit) { commitSettings(); willCommit=false; } if (shallDetectScale) { if (--shallDetectScale<1) { if (settings.dpiScale<0.5f) { const char* videoBackend=SDL_GetCurrentVideoDriver(); double newScale=getScaleFactor(videoBackend,sdlWin); if (newScale<0.1f) { logW("scale what?"); newScale=1.0f; } if (newScale!=dpiScale) { logD("auto UI scale changed (%f != %f) - applying settings...",newScale,dpiScale); ImGui::GetIO().Fonts->Clear(); applyUISettings(); if (rend) rend->destroyFontsTexture(); if (!ImGui::GetIO().Fonts->Build()) { logE("error while building font atlas!"); showError("error while loading fonts! please check your settings."); ImGui::GetIO().Fonts->Clear(); mainFont=ImGui::GetIO().Fonts->AddFontDefault(); patFont=mainFont; bigFont=mainFont; headFont=mainFont; if (rend) rend->destroyFontsTexture(); if (!ImGui::GetIO().Fonts->Build()) { logE("error again while building font atlas!"); } else { rend->createFontsTexture(); } } else { rend->createFontsTexture(); } } } } } if (fontsFailed) { showError("it appears I couldn't load these fonts. any setting you can check?"); logE("couldn't load fonts"); ImGui::GetIO().Fonts->Clear(); mainFont=ImGui::GetIO().Fonts->AddFontDefault(); patFont=mainFont; bigFont=mainFont; headFont=mainFont; if (rend) rend->destroyFontsTexture(); if (!ImGui::GetIO().Fonts->Build()) { logE("error again while building font atlas!"); } else { rend->createFontsTexture(); } } if (!editOptsVisible) { latchTarget=0; latchNibble=false; } if (SDL_GetWindowFlags(sdlWin)&SDL_WINDOW_MINIMIZED) { SDL_Delay(100); } } return false; } bool FurnaceGUI::init() { logI("initializing GUI."); String homeDir=getHomeDir(); workingDir=e->getConfString("lastDir",homeDir); workingDirSong=e->getConfString("lastDirSong",workingDir); workingDirIns=e->getConfString("lastDirIns",workingDir); workingDirWave=e->getConfString("lastDirWave",workingDir); workingDirSample=e->getConfString("lastDirSample",workingDir); workingDirAudioExport=e->getConfString("lastDirAudioExport",workingDir); workingDirVGMExport=e->getConfString("lastDirVGMExport",workingDir); workingDirZSMExport=e->getConfString("lastDirZSMExport",workingDir); workingDirROMExport=e->getConfString("lastDirROMExport",workingDir); workingDirFont=e->getConfString("lastDirFont",workingDir); workingDirColors=e->getConfString("lastDirColors",workingDir); workingDirKeybinds=e->getConfString("lastDirKeybinds",workingDir); workingDirLayout=e->getConfString("lastDirLayout",workingDir); workingDirTest=e->getConfString("lastDirTest",workingDir); editControlsOpen=e->getConfBool("editControlsOpen",true); ordersOpen=e->getConfBool("ordersOpen",true); insListOpen=e->getConfBool("insListOpen",true); songInfoOpen=e->getConfBool("songInfoOpen",true); patternOpen=e->getConfBool("patternOpen",true); insEditOpen=e->getConfBool("insEditOpen",false); waveListOpen=e->getConfBool("waveListOpen",true); waveEditOpen=e->getConfBool("waveEditOpen",false); sampleListOpen=e->getConfBool("sampleListOpen",true); sampleEditOpen=e->getConfBool("sampleEditOpen",false); settingsOpen=e->getConfBool("settingsOpen",false); mixerOpen=e->getConfBool("mixerOpen",false); oscOpen=e->getConfBool("oscOpen",true); chanOscOpen=e->getConfBool("chanOscOpen",false); volMeterOpen=e->getConfBool("volMeterOpen",true); statsOpen=e->getConfBool("statsOpen",false); compatFlagsOpen=e->getConfBool("compatFlagsOpen",false); #ifdef IS_MOBILE pianoOpen=e->getConfBool("pianoOpen",true); #else pianoOpen=e->getConfBool("pianoOpen",false); #endif notesOpen=e->getConfBool("notesOpen",false); channelsOpen=e->getConfBool("channelsOpen",false); patManagerOpen=e->getConfBool("patManagerOpen",false); sysManagerOpen=e->getConfBool("sysManagerOpen",false); clockOpen=e->getConfBool("clockOpen",false); speedOpen=e->getConfBool("speedOpen",true); groovesOpen=e->getConfBool("groovesOpen",false); regViewOpen=e->getConfBool("regViewOpen",false); logOpen=e->getConfBool("logOpen",false); effectListOpen=e->getConfBool("effectListOpen",true); subSongsOpen=e->getConfBool("subSongsOpen",true); findOpen=e->getConfBool("findOpen",false); spoilerOpen=e->getConfBool("spoilerOpen",false); if (e->hasConf("lastDir")) { basicMode=e->getConfBool("basicMode",false); } else { basicMode=true; } insListDir=e->getConfBool("insListDir",false); waveListDir=e->getConfBool("waveListDir",false); sampleListDir=e->getConfBool("sampleListDir",false); tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); waveSigned=e->getConfBool("waveSigned",false); waveGenVisible=e->getConfBool("waveGenVisible",false); waveEditStyle=e->getConfInt("waveEditStyle",0); extraChannelButtons=e->getConfInt("extraChannelButtons",0); lockLayout=e->getConfBool("lockLayout",false); #ifdef IS_MOBILE fullScreen=true; #else fullScreen=e->getConfBool("fullScreen",false); #endif mobileUI=e->getConfBool("mobileUI",MOBILE_UI_DEFAULT); edit=e->getConfBool("edit",false); followOrders=e->getConfBool("followOrders",true); followPattern=e->getConfBool("followPattern",true); noteInputPoly=e->getConfBool("noteInputPoly",true); exportLoops=e->getConfInt("exportLoops",0); if (exportLoops<0) exportLoops=0; exportFadeOut=e->getConfDouble("exportFadeOut",0.0); if (exportFadeOut<0.0) exportFadeOut=0.0; orderEditMode=e->getConfInt("orderEditMode",0); if (orderEditMode<0) orderEditMode=0; if (orderEditMode>3) orderEditMode=3; oscZoom=e->getConfFloat("oscZoom",0.5f); oscZoomSlider=e->getConfBool("oscZoomSlider",false); oscWindowSize=e->getConfFloat("oscWindowSize",20.0f); pianoOctaves=e->getConfInt("pianoOctaves",pianoOctaves); pianoOctavesEdit=e->getConfInt("pianoOctavesEdit",pianoOctavesEdit); pianoOptions=e->getConfBool("pianoOptions",pianoOptions); pianoSharePosition=e->getConfBool("pianoSharePosition",pianoSharePosition); pianoOptionsSet=e->getConfBool("pianoOptionsSet",pianoOptionsSet); pianoReadonly=e->getConfBool("pianoReadonly",false); pianoOffset=e->getConfInt("pianoOffset",pianoOffset); pianoOffsetEdit=e->getConfInt("pianoOffsetEdit",pianoOffsetEdit); pianoView=e->getConfInt("pianoView",pianoView); pianoInputPadMode=e->getConfInt("pianoInputPadMode",pianoInputPadMode); chanOscCols=e->getConfInt("chanOscCols",3); chanOscAutoColsType=e->getConfInt("chanOscAutoColsType",0); chanOscColorX=e->getConfInt("chanOscColorX",GUI_OSCREF_CENTER); chanOscColorY=e->getConfInt("chanOscColorY",GUI_OSCREF_CENTER); chanOscTextX=e->getConfFloat("chanOscTextX",0.0f); chanOscTextY=e->getConfFloat("chanOscTextY",0.0f); chanOscAmplify=e->getConfFloat("chanOscAmplify",0.95f); chanOscWindowSize=e->getConfFloat("chanOscWindowSize",20.0f); chanOscWaveCorr=e->getConfBool("chanOscWaveCorr",true); chanOscOptions=e->getConfBool("chanOscOptions",false); chanOscNormalize=e->getConfBool("chanOscNormalize",false); chanOscTextFormat=e->getConfString("chanOscTextFormat","%c"); chanOscColor.x=e->getConfFloat("chanOscColorR",1.0f); chanOscColor.y=e->getConfFloat("chanOscColorG",1.0f); chanOscColor.z=e->getConfFloat("chanOscColorB",1.0f); chanOscColor.w=e->getConfFloat("chanOscColorA",1.0f); chanOscTextColor.x=e->getConfFloat("chanOscTextColorR",1.0f); chanOscTextColor.y=e->getConfFloat("chanOscTextColorG",1.0f); chanOscTextColor.z=e->getConfFloat("chanOscTextColorB",1.0f); chanOscTextColor.w=e->getConfFloat("chanOscTextColorA",0.75f); chanOscUseGrad=e->getConfBool("chanOscUseGrad",false); chanOscGrad.fromString(e->getConfString("chanOscGrad","")); chanOscGrad.render(); syncSettings(); syncTutorial(); if (!settings.persistFadeOut) { exportLoops=settings.exportLoops; exportFadeOut=settings.exportFadeOut; } for (int i=0; igetConfString(fmt::sprintf("recentFile%d",i),""); if (!r.empty()) { recentFile.push_back(r); } } initSystemPresets(); initTutorial(); e->setAutoNotePoly(noteInputPoly); SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER,"1"); #if SDL_VERSION_ATLEAST(2,0,17) SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS,"0"); #endif SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS,"0"); // don't disable compositing on KWin #if SDL_VERSION_ATLEAST(2,0,22) logV("setting window type to NORMAL."); SDL_SetHint(SDL_HINT_X11_WINDOW_TYPE,"_NET_WM_WINDOW_TYPE_NORMAL"); #endif // initialize SDL logD("initializing video..."); if (SDL_Init(SDL_INIT_VIDEO)!=0) { logE("could not initialize video! %s",SDL_GetError()); return false; } #ifdef IS_MOBILE logD("initializing haptic..."); if (SDL_Init(SDL_INIT_HAPTIC)!=0) { logW("could not initialize haptic! %s",SDL_GetError()); } #endif const char* videoBackend=SDL_GetCurrentVideoDriver(); if (videoBackend!=NULL) { logV("video backend: %s",videoBackend); if (strcmp(videoBackend,"wayland")==0 || strcmp(videoBackend,"cocoa")==0 || strcmp(videoBackend,"uikit")==0) { sysManagedScale=true; logV("scaling managed by system."); } else { logV("scaling managed by application."); } } else { logV("could not get video backend name!"); } // get scale factor if (settings.dpiScale>=0.5f) { logD("setting UI scale factor from config (%f).",settings.dpiScale); dpiScale=settings.dpiScale; } else { logD("auto-detecting UI scale factor."); dpiScale=getScaleFactor(videoBackend,sdlWin); logD("scale factor: %f",dpiScale); if (dpiScale<0.1f) { logW("scale what?"); dpiScale=1.0f; } } #if !(defined(__APPLE__) || defined(_WIN32)) // get the icon (on macOS and Windows the icon is bundled with the app) const FurnaceGUIImage* furIcon=getImage(GUI_IMAGE_ICON); SDL_Surface* icon=NULL; if (furIcon!=NULL) { icon=SDL_CreateRGBSurfaceFrom(furIcon->data,furIcon->width,furIcon->height,32,256*4,0xff,0xff00,0xff0000,0xff000000); } else { logE("furIcon is NULL!"); } #endif #ifdef IS_MOBILE scrW=960; scrH=540; scrX=0; scrY=0; #else scrW=scrConfW=e->getConfInt("lastWindowWidth",1280); scrH=scrConfH=e->getConfInt("lastWindowHeight",800); scrX=scrConfX=e->getConfInt("lastWindowX",SDL_WINDOWPOS_CENTERED); scrY=scrConfY=e->getConfInt("lastWindowY",SDL_WINDOWPOS_CENTERED); scrMax=e->getConfBool("lastWindowMax",false); #endif portrait=(scrWgetConfInt("configVersion",0)<122 && !sysManagedScale) { logD("scaling window size to scale factor because configVersion is not present."); scrW*=dpiScale; scrH*=dpiScale; } // predict the canvas size if (sysManagedScale) { canvasW=scrW*dpiScale; canvasH=scrH*dpiScale; } else { canvasW=scrW; canvasH=scrH; } #ifndef IS_MOBILE SDL_Rect displaySize; #endif #ifndef IS_MOBILE // if window would spawn out of bounds, force it to be get default position SDL_Rect bounds; if (!detectOutOfBoundsWindow(bounds)) { scrMax=false; scrX=scrConfX=SDL_WINDOWPOS_CENTERED; scrY=scrConfY=SDL_WINDOWPOS_CENTERED; // make sure our window isn't big /*if (bounds.wsetConf("renderBackend","SDL"); e->saveConf(); lastError=fmt::sprintf("could not init renderer!\r\nthe render backend has been set to a safe value. please restart Furnace."); } else { lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError()); if (!settings.renderDriver.empty()) { settings.renderDriver=""; e->setConf("renderDriver",""); e->saveConf(); lastError+=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace."); } } return false; } rend->preInit(); logD("creating window..."); sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW,scrH,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0)|rend->getWindowFlags()); if (sdlWin==NULL) { lastError=fmt::sprintf("could not open window! %s",SDL_GetError()); return false; } #ifndef IS_MOBILE if (SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(sdlWin),&displaySize)==0) { bool mustChange=false; if (scrW>((displaySize.w)-48) && scrH>((displaySize.h)-64)) { // maximize SDL_MaximizeWindow(sdlWin); logD("maximizing as it doesn't fit (%dx%d+%d+%d).",displaySize.w,displaySize.h,displaySize.x,displaySize.y); } if (scrW>displaySize.w) { scrW=(displaySize.w)-32; mustChange=true; } if (scrH>displaySize.h) { scrH=(displaySize.h)-32; mustChange=true; } if (mustChange) { portrait=(scrWinit(sdlWin)) { logE("it failed..."); if (settings.renderBackend!="SDL") { settings.renderBackend="SDL"; e->setConf("renderBackend","SDL"); e->saveConf(); lastError=fmt::sprintf("could not init renderer!\r\nthe render backend has been set to a safe value. please restart Furnace."); } else { lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError()); if (!settings.renderDriver.empty()) { settings.renderDriver=""; e->setConf("renderDriver",""); e->saveConf(); lastError+=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace."); } } return false; } logV("render backend started"); // try acquiring the canvas size if (!rend->getOutputSize(canvasW,canvasH)) { logW("could not get renderer output size!"); } else { logV("canvas size: %dx%d",canvasW,canvasH); } // special consideration for Wayland if (settings.dpiScale<0.5f) { if (strcmp(videoBackend,"wayland")==0) { int realW=scrW; int realH=scrH; SDL_GetWindowSize(sdlWin,&realW,&realH); if (realW<1) { logW("screen width is zero!\n"); dpiScale=1.0; } else { dpiScale=(double)canvasW/(double)realW; logV("we're on Wayland... scaling factor: %f",dpiScale); } } } updateWindowTitle(); rend->clear(ImVec4(0.0,0.0,0.0,1.0)); rend->present(); logD("preparing user interface..."); IMGUI_CHECKVERSION(); ImGui::CreateContext(); rend->initGUI(sdlWin); applyUISettings(); logD("building font..."); if (!ImGui::GetIO().Fonts->Build()) { logE("error while building font atlas!"); showError("error while loading fonts! please check your settings."); ImGui::GetIO().Fonts->Clear(); mainFont=ImGui::GetIO().Fonts->AddFontDefault(); patFont=mainFont; bigFont=mainFont; headFont=mainFont; if (rend) rend->destroyFontsTexture(); if (!ImGui::GetIO().Fonts->Build()) { logE("error again while building font atlas!"); } } logD("preparing layout..."); strncpy(finalLayoutPath,(e->getConfigPath()+String(LAYOUT_INI)).c_str(),4095); backupPath=e->getConfigPath(); if (backupPath.size()>0) { if (backupPath[backupPath.size()-1]==DIR_SEPARATOR) backupPath.resize(backupPath.size()-1); } backupPath+=String(BACKUPS_DIR); prepareLayout(); ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_DockingEnable; toggleMobileUI(mobileUI,true); for (int i=0; isetMidiCallback([this](const TAMidiMessage& msg) -> int { if (introPos<11.0) return -2; midiLock.lock(); midiQueue.push(msg); midiLock.unlock(); e->setMidiBaseChan(cursor.xCoarse); if (msg.type==TA_MIDI_SYSEX) return -2; if (midiMap.valueInputStyle!=0 && cursor.xFine!=0 && edit) return -2; if (!midiMap.noteInput) return -2; if (learning!=-1) return -2; if (midiMap.at(msg)) return -2; if (curWindowThreadSafe==GUI_WINDOW_WAVE_EDIT || curWindowThreadSafe==GUI_WINDOW_WAVE_LIST) { if ((msg.type&0xf0)==TA_MIDI_NOTE_ON) { e->previewWaveNoLock(curWave,msg.data[0]-12); wavePreviewNote=msg.data[0]-12; } else if ((msg.type&0xf0)==TA_MIDI_NOTE_OFF) { if (wavePreviewNote==msg.data[0]-12) { e->stopWavePreviewNoLock(); } } return -2; } if (curWindowThreadSafe==GUI_WINDOW_SAMPLE_EDIT || curWindowThreadSafe==GUI_WINDOW_SAMPLE_LIST) { if ((msg.type&0xf0)==TA_MIDI_NOTE_ON) { e->previewSampleNoLock(curSample,msg.data[0]-12); samplePreviewNote=msg.data[0]-12; } else if ((msg.type&0xf0)==TA_MIDI_NOTE_OFF) { if (samplePreviewNote==msg.data[0]-12) { e->stopSamplePreviewNoLock(); } } return -2; } return curIns; }); #ifdef IS_MOBILE vibrator=SDL_HapticOpen(0); if (vibrator==NULL) { logD("could not open vibration device: %s",SDL_GetError()); } else { if (SDL_HapticRumbleInit(vibrator)==0) { vibratorAvailable=true; } else { logD("vibration not available: %s",SDL_GetError()); } } #endif cpuCores=SDL_GetCPUCount(); if (cpuCores<1) cpuCores=1; logI("done!"); return true; } void FurnaceGUI::commitState() { if (!mobileUI) { if (!ImGui::SaveIniSettingsToDisk(finalLayoutPath,true)) { reportError(fmt::sprintf("could NOT save layout! %s",strerror(errno))); } } e->setConf("configVersion",(int)DIV_ENGINE_VERSION); e->setConf("lastDir",workingDir); e->setConf("lastDirSong",workingDirSong); e->setConf("lastDirIns",workingDirIns); e->setConf("lastDirWave",workingDirWave); e->setConf("lastDirSample",workingDirSample); e->setConf("lastDirAudioExport",workingDirAudioExport); e->setConf("lastDirVGMExport",workingDirVGMExport); e->setConf("lastDirZSMExport",workingDirZSMExport); e->setConf("lastDirROMExport",workingDirROMExport); e->setConf("lastDirFont",workingDirFont); e->setConf("lastDirColors",workingDirColors); e->setConf("lastDirKeybinds",workingDirKeybinds); e->setConf("lastDirLayout",workingDirLayout); e->setConf("lastDirTest",workingDirTest); // commit last open windows e->setConf("editControlsOpen",editControlsOpen); e->setConf("ordersOpen",ordersOpen); e->setConf("insListOpen",insListOpen); e->setConf("songInfoOpen",songInfoOpen); e->setConf("patternOpen",patternOpen); e->setConf("insEditOpen",insEditOpen); e->setConf("waveListOpen",waveListOpen); e->setConf("waveEditOpen",waveEditOpen); e->setConf("sampleListOpen",sampleListOpen); e->setConf("sampleEditOpen",sampleEditOpen); e->setConf("settingsOpen",settingsOpen); e->setConf("mixerOpen",mixerOpen); e->setConf("oscOpen",oscOpen); e->setConf("chanOscOpen",chanOscOpen); e->setConf("volMeterOpen",volMeterOpen); e->setConf("statsOpen",statsOpen); e->setConf("compatFlagsOpen",compatFlagsOpen); e->setConf("pianoOpen",pianoOpen); e->setConf("notesOpen",notesOpen); e->setConf("channelsOpen",channelsOpen); e->setConf("patManagerOpen",patManagerOpen); e->setConf("sysManagerOpen",sysManagerOpen); e->setConf("clockOpen",clockOpen); e->setConf("speedOpen",speedOpen); e->setConf("groovesOpen",groovesOpen); e->setConf("regViewOpen",regViewOpen); e->setConf("logOpen",logOpen); e->setConf("effectListOpen",effectListOpen); e->setConf("subSongsOpen",subSongsOpen); e->setConf("findOpen",findOpen); e->setConf("spoilerOpen",spoilerOpen); e->setConf("basicMode",basicMode); // commit dir state e->setConf("insListDir",insListDir); e->setConf("waveListDir",waveListDir); e->setConf("sampleListDir",sampleListDir); // commit last window size e->setConf("lastWindowWidth",scrConfW); e->setConf("lastWindowHeight",scrConfH); e->setConf("lastWindowX",settings.saveWindowPos?scrConfX:(int)SDL_WINDOWPOS_CENTERED); e->setConf("lastWindowY",settings.saveWindowPos?scrConfY:(int)SDL_WINDOWPOS_CENTERED); e->setConf("lastWindowMax",scrMax); e->setConf("tempoView",tempoView); e->setConf("waveHex",waveHex); e->setConf("waveSigned",waveSigned); e->setConf("waveGenVisible",waveGenVisible); e->setConf("waveEditStyle",waveEditStyle); e->setConf("extraChannelButtons",extraChannelButtons); e->setConf("lockLayout",lockLayout); e->setConf("fullScreen",fullScreen); e->setConf("mobileUI",mobileUI); e->setConf("edit",edit); e->setConf("followOrders",followOrders); e->setConf("followPattern",followPattern); e->setConf("orderEditMode",orderEditMode); e->setConf("noteInputPoly",noteInputPoly); if (settings.persistFadeOut) { e->setConf("exportLoops",exportLoops); e->setConf("exportFadeOut",exportFadeOut); } // commit oscilloscope state e->setConf("oscZoom",oscZoom); e->setConf("oscZoomSlider",oscZoomSlider); e->setConf("oscWindowSize",oscWindowSize); // commit piano state e->setConf("pianoOctaves",pianoOctaves); e->setConf("pianoOctavesEdit",pianoOctavesEdit); e->setConf("pianoOptions",pianoOptions); e->setConf("pianoSharePosition",pianoSharePosition); e->setConf("pianoOptionsSet",pianoOptionsSet); e->setConf("pianoReadonly",pianoReadonly); e->setConf("pianoOffset",pianoOffset); e->setConf("pianoOffsetEdit",pianoOffsetEdit); e->setConf("pianoView",pianoView); e->setConf("pianoInputPadMode",pianoInputPadMode); // commit per-chan osc state e->setConf("chanOscCols",chanOscCols); e->setConf("chanOscAutoColsType",chanOscAutoColsType); e->setConf("chanOscColorX",chanOscColorX); e->setConf("chanOscColorY",chanOscColorY); e->setConf("chanOscTextX",chanOscTextX); e->setConf("chanOscTextY",chanOscTextY); e->setConf("chanOscAmplify",chanOscAmplify); e->setConf("chanOscWindowSize",chanOscWindowSize); e->setConf("chanOscWaveCorr",chanOscWaveCorr); e->setConf("chanOscOptions",chanOscOptions); e->setConf("chanOscNormalize",chanOscNormalize); e->setConf("chanOscTextFormat",chanOscTextFormat); e->setConf("chanOscColorR",chanOscColor.x); e->setConf("chanOscColorG",chanOscColor.y); e->setConf("chanOscColorB",chanOscColor.z); e->setConf("chanOscColorA",chanOscColor.w); e->setConf("chanOscTextColorR",chanOscTextColor.x); e->setConf("chanOscTextColorG",chanOscTextColor.y); e->setConf("chanOscTextColorB",chanOscTextColor.z); e->setConf("chanOscTextColorA",chanOscTextColor.w); e->setConf("chanOscUseGrad",chanOscUseGrad); e->setConf("chanOscGrad",chanOscGrad.toString()); // commit recent files for (int i=0; i<30; i++) { String key=fmt::sprintf("recentFile%d",i); if (i>=settings.maxRecentFile || i>=(int)recentFile.size()) { e->setConf(key,""); } else { e->setConf(key,recentFile[i]); } } } bool FurnaceGUI::finish() { commitState(); rend->quitGUI(); ImGui_ImplSDL2_Shutdown(); quitRender(); ImGui::DestroyContext(); SDL_DestroyWindow(sdlWin); if (vibrator) { SDL_HapticClose(vibrator); } for (int i=0; i