#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-2025 tildearrow and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ // 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" #ifdef HAVE_SNDFILE #include #endif 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); } void FurnaceGUI::enableSafeMode() { safeMode=true; } 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; MARK_MODIFIED; } } #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::isCtrlWheelModifierHeld() const { switch (settings.ctrlWheelModifier) { case 0: return ImGui::IsKeyDown(ImGuiMod_Ctrl) || ImGui::IsKeyDown(ImGuiMod_Super); case 1: return ImGui::IsKeyDown(ImGuiMod_Ctrl); case 2: return ImGui::IsKeyDown(ImGuiMod_Super); case 3: return ImGui::IsKeyDown(ImGuiMod_Alt); default: return false; } } 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; } bool FurnaceGUI::LocalizedComboGetter(void* data, int idx, const char** out_text) { const char* const* items=(const char* const*)data; if (out_text) *out_text=_(items[idx]); return true; } 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=(PAT_FONT_SIZE+2*dpiScale); nextScroll=lineHeight*amount; haveHitBounds=false; } void FurnaceGUI::updateScrollRaw(float amount) { nextScroll=amount; haveHitBounds=false; } void FurnaceGUI::addScroll(int amount) { float lineHeight=(PAT_FONT_SIZE+2*dpiScale); nextAddScroll=lineHeight*amount; haveHitBounds=false; } void FurnaceGUI::addScrollX(int amount) { float lineHeight=(PAT_FONT_SIZE+2*dpiScale); nextAddScrollX=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()); if (e->song.insLen==1) { unsigned int checker=0x11111111; unsigned int checker1=0; DivInstrument* ins=e->getIns(0); if (ins->name.size()==15 && e->curSubSong->ordersLen==8) { for (int i=0; i<15; i++) { checker^=ins->name[i]<name[i]; checker=(checker>>1|(((checker)^(checker>>2)^(checker>>3)^(checker>>5))&1)<<31); checker1<<=1; } if (checker==0x5ec4497d && checker1==0x6347ee) nonLatchNibble=true; } } } void FurnaceGUI::autoDetectSystemIter(std::vector& category, bool& isMatch, std::map& defCountMap, std::map& defConfMap, std::map& sysCountMap, std::map& sysConfMap) { for (FurnaceGUISysDef& j: category) { if (!j.orig.empty()) { 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.c_str()); defConfMap[k.sys]=dc; } if (defCountMap.size()==sysCountMap.size()) { 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 (!j.subDefs.empty()) autoDetectSystemIter(j.subDefs,isMatch,defCountMap,defConfMap,sysCountMap,sysConfMap); if (isMatch) break; } } 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) { autoDetectSystemIter(i.systems,isMatch,defCountMap,defConfMap,sysCountMap,sysConfMap); 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; } } } void FurnaceGUI::updateROMExportAvail() { memset(romExportAvail,0,sizeof(bool)*DIV_ROM_MAX); romExportExists=false; for (int i=0; iisROMExportViable((DivROMExportOptions)i)) { romExportAvail[i]=true; romExportExists=true; } } if (!romExportAvail[romTarget]) { // find a new one romTarget=DIV_ROM_ABSTRACT; for (int i=0; igetROMExportDef((DivROMExportOptions)i); if (newDef!=NULL) { if (romExportAvail[i]) { romTarget=(DivROMExportOptions)i; romMultiFile=newDef->multiOutput; romConfig=DivConfig(); if (newDef->fileExt==NULL) { romFilterName=""; romFilterExt=""; } else { romFilterName=newDef->fileType; romFilterExt=newDef->fileExt; } break; } } } } } 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][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\ [Window][Oscilloscope (X-Y)]\n\ Pos=60,60\n\ Size=300,300\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; igetStreamPlayer()) { e->killStream(); } memset(chanOscVol,0,DIV_MAX_CHANS*sizeof(float)); for (int i=0; iwalkSong(loopOrder,loopRow,loopEnd); memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS); if (wasFollowing) { followPattern=true; wasFollowing=false; } if (followPattern) makeCursorUndo(); if (!followPattern) e->setOrder(curOrder); if (row>=0) { if (!e->playToRow(row)) { showError(_("the song is over!")); } } else { if (!e->play()) { showError(_("the song is over!")); } } 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; e->getPlayPos(curOrder, cursor.y); if (selStart.xCoarse==selEnd.xCoarse && selStart.xFine==selEnd.xFine && selStart.y==selEnd.y && selStart.order==selEnd.order && !selecting) { selStart=cursor; selEnd=cursor; } updateScroll(cursor.y); } if (wasFollowing) { followPattern = true; wasFollowing = false; } } void FurnaceGUI::previewNote(int refChan, int note, bool autoNote) { e->setMidiBaseChan(refChan); e->synchronized([this,note]() { if (!e->autoNoteOn(-1,curIns,note)) failedNoteOn=true; }); } 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); failedNoteOn=false; }); } } void FurnaceGUI::noteInput(int num, int key, int vol) { int ch=cursor.xCoarse; int ord=curOrder; int y=cursor.y; int tick=0; int speed=0; if (e->isPlaying() && !e->isStepping() && followPattern) { e->getPlayPosTick(ord,y,tick,speed); if (tick<=(speed/2)) { // round // TODO: detect 0Dxx/0Bxx? if (++y>=e->curSubSong->patLen) { y=0; if (++ord>=e->curSubSong->ordersLen) { ord=0; } } } } logV("chan %d, %d:%d %d/%d",ch,ord,y,tick,speed); DivPattern* pat=e->curPat[ch].getPattern(e->curOrders->ord[ch][ord],true); bool removeIns=false; prepareUndo(GUI_UNDO_PATTERN_EDIT); if (key==GUI_NOTE_OFF) { // note off pat->data[y][0]=100; pat->data[y][1]=0; removeIns=true; } else if (key==GUI_NOTE_OFF_RELEASE) { // note off + env release pat->data[y][0]=101; pat->data[y][1]=0; removeIns=true; } else if (key==GUI_NOTE_RELEASE) { // env release only pat->data[y][0]=102; pat->data[y][1]=0; removeIns=true; } else { pat->data[y][0]=num%12; pat->data[y][1]=num/12; if (pat->data[y][0]==0) { pat->data[y][0]=12; pat->data[y][1]--; } pat->data[y][1]=(unsigned char)pat->data[y][1]; if (latchIns==-2) { if (curIns>=(int)e->song.ins.size()) curIns=-1; if (curIns>=0) { pat->data[y][2]=curIns; } } else if (latchIns!=-1 && !e->song.ins.empty()) { pat->data[y][2]=MIN(((int)e->song.ins.size())-1,latchIns); } int maxVol=e->getMaxVolumeChan(ch); if (latchVol!=-1) { pat->data[y][3]=MIN(maxVol,latchVol); } else if (vol!=-1) { pat->data[y][3]=e->mapVelocity(ch,pow((float)vol/127.0f,midiMap.volExp)); } if (latchEffect!=-1) pat->data[y][4]=latchEffect; if (latchEffectVal!=-1) pat->data[y][5]=latchEffectVal; } if (removeIns) { if (settings.removeInsOff) { pat->data[y][2]=-1; } if (settings.removeVolOff) { pat->data[y][3]=-1; } } editAdvance(); makeUndo(GUI_UNDO_PATTERN_EDIT); curNibble=false; } void FurnaceGUI::valueInput(int num, bool direct, int target) { int ch=cursor.xCoarse; int ord=curOrder; int y=cursor.y; if (e->isPlaying() && !e->isStepping() && followPattern) { e->getPlayPos(ord,y); } DivPattern* pat=e->curPat[ch].getPattern(e->curOrders->ord[ch][ord],true); prepareUndo(GUI_UNDO_PATTERN_EDIT); if (target==-1) target=cursor.xFine+1; if (direct) { pat->data[y][target]=num&0xff; } else { if (pat->data[y][target]==-1) pat->data[y][target]=0; if (!settings.pushNibble && !curNibble) { pat->data[y][target]=num; } else { pat->data[y][target]=((pat->data[y][target]<<4)|num)&0xff; } } if (cursor.xFine==1) { // instrument if (pat->data[y][target]>=(int)e->song.ins.size()) { pat->data[y][target]&=0x0f; if (pat->data[y][target]>=(int)e->song.ins.size()) { pat->data[y][target]=(int)e->song.ins.size()-1; } } if (settings.absorbInsInput) { curIns=pat->data[y][target]; wavePreviewInit=true; updateFMPreview=true; } if (direct) { curNibble=false; } else { if (e->song.ins.size()<16) { curNibble=false; editAdvance(); } else { curNibble=!curNibble; if (!curNibble) editAdvance(); } } makeUndo(GUI_UNDO_PATTERN_EDIT); } else if (cursor.xFine==2) { if (curNibble) { if (pat->data[y][target]>e->getMaxVolumeChan(ch)) pat->data[y][target]=e->getMaxVolumeChan(ch); } else { pat->data[y][target]&=15; } if (direct) { curNibble=false; } else { if (e->getMaxVolumeChan(ch)<16) { curNibble=false; if (pat->data[y][target]>e->getMaxVolumeChan(ch)) pat->data[y][target]=e->getMaxVolumeChan(ch); editAdvance(); } else { curNibble=!curNibble; if (!curNibble) editAdvance(); } } makeUndo(GUI_UNDO_PATTERN_EDIT); } else { if (direct) { curNibble=false; } else { curNibble=!curNibble; if (!curNibble) { if (!settings.effectCursorDir) { editAdvance(); } else { if (settings.effectCursorDir==2) { if (++cursor.xFine>=(3+(e->curPat[ch].effectCols*2))) { cursor.xFine=3; } } else { if (cursor.xFine&1) { cursor.xFine++; } else { editAdvance(); cursor.xFine--; } } } } } makeUndo(GUI_UNDO_PATTERN_EDIT); } } void FurnaceGUI::orderInput(int num) { 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); } } #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 (aboutOpen) return; if (cvOpen) 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 (!ImGuiFileDialog::Instance()->IsOpened()) { 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][bindSetTargetIdx]=(mapped&(~FURK_MASK))|0xffffff; break; default: actionKeys[bindSetTarget][bindSetTargetIdx]=mapped; // de-dupe with an n^2 algorithm that will never ever be a problem (...but for real though) for (size_t i=0; isecond; 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) { switch (sampleMapColumn) { case 0: { if (ev.key.keysym.scancode==SDL_SCANCODE_DELETE) { alterSampleMap(0,-1); return; } auto it=valueKeys.find(ev.key.keysym.sym); if (it!=valueKeys.cend()) { int num=it->second; if (num<10) { alterSampleMap(0,num); return; } } break; } case 1: { if (ev.key.keysym.scancode==SDL_SCANCODE_DELETE) { alterSampleMap(1,-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(1,num); return; } break; } case 2: { if (ev.key.keysym.scancode==SDL_SCANCODE_DELETE) { alterSampleMap(2,-1); return; } auto it=valueKeys.find(ev.key.keysym.sym); if (it!=valueKeys.cend()) { int num=it->second; if (num<10) { alterSampleMap(2,num); return; } } break; } case 3: { if (ev.key.keysym.scancode==SDL_SCANCODE_DELETE) { alterSampleMap(3,-1); return; } auto it=valueKeys.find(ev.key.keysym.sym); if (it!=valueKeys.cend()) { int num=it->second; if (num<16) { alterSampleMap(3,num); return; } } break; } } } // 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 || settings.inputRepeat) { 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; orderInput(num); } } 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) { if (ImGuiFileDialog::Instance()->IsOpened()) { if (action!=GUI_ACTION_OCTAVE_UP && action!=GUI_ACTION_OCTAVE_DOWN) return; } 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; String shortName; size_t shortNamePos=curFileName.rfind(DIR_SEPARATOR); if (shortNamePos!=String::npos && (shortNamePos+1)openLoad( _("Open File"), {_("compatible files"), "*.fur *.dmf *.mod *.s3m *.xm *.it *.fc13 *.fc14 *.smod *.fc *.ftm *.0cc *.dnm *.eft *.fub *.tfe", _("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( _("Export DMF"), {_("DefleMask 1.1.3 module"), "*.dmf"}, workingDirSong, dpiScale, (settings.autoFillSave)?shortName:"" ); break; case GUI_FILE_SAVE_DMF_LEGACY: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openSave( _("Export DMF"), {_("DefleMask 1.0/legacy module"), "*.dmf"}, workingDirSong, dpiScale, (settings.autoFillSave)?shortName:"" ); 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, (settings.autoFillSave)?e->getIns(curIns)->name:"" ); break; case GUI_FILE_INS_SAVE_DMP: if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); hasOpened=fileDialog->openSave( _("Save Instrument"), {_("DefleMask preset"), "*.dmp"}, workingDirIns, dpiScale, (settings.autoFillSave)?e->getIns(curIns)->name:"" ); break; case GUI_FILE_INS_SAVE_ALL: if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); hasOpened=fileDialog->openSelectDir( _("Save All Instruments"), 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_WAVE_SAVE_ALL: if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); hasOpened=fileDialog->openSelectDir( _("Save All Wavetables"), workingDirWave, dpiScale ); break; case GUI_FILE_SAMPLE_OPEN: case GUI_FILE_SAMPLE_OPEN_REPLACE: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); hasOpened=fileDialog->openLoad( _("Load Sample"), audioLoadFormats, 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, (settings.autoFillSave)?e->getSample(curSample)->name:"" ); break; case GUI_FILE_SAMPLE_SAVE_RAW: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); hasOpened=fileDialog->openSave( _("Save Raw Sample"), {_("all files"), "*"}, workingDirSample, dpiScale, (settings.autoFillSave)?e->getSample(curSample)->name:"" ); break; case GUI_FILE_SAMPLE_SAVE_ALL: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); hasOpened=fileDialog->openSelectDir( _("Save All Samples"), workingDirSample, dpiScale ); break; case GUI_FILE_EXPORT_AUDIO_ONE: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); hasOpened=fileDialog->openSave( _("Export Audio"), {_("Wave file"), "*.wav"}, workingDirAudioExport, dpiScale, (settings.autoFillSave)?shortName:"" ); break; case GUI_FILE_EXPORT_AUDIO_PER_SYS: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); hasOpened=fileDialog->openSave( _("Export Audio"), {_("Wave file"), "*.wav"}, workingDirAudioExport, dpiScale, (settings.autoFillSave)?shortName:"" ); break; case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); hasOpened=fileDialog->openSave( _("Export Audio"), {_("Wave file"), "*.wav"}, workingDirAudioExport, dpiScale, (settings.autoFillSave)?shortName:"" ); break; case GUI_FILE_EXPORT_VGM: if (!dirExists(workingDirVGMExport)) workingDirVGMExport=getHomeDir(); hasOpened=fileDialog->openSave( _("Export VGM"), {_("VGM file"), "*.vgm"}, workingDirVGMExport, dpiScale, (settings.autoFillSave)?shortName:"" ); break; case GUI_FILE_EXPORT_TEXT: if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir(); hasOpened=fileDialog->openSave( _("Export Command Stream"), {_("text file"), "*.txt"}, workingDirROMExport, dpiScale, (settings.autoFillSave)?shortName:"" ); break; case GUI_FILE_EXPORT_CMDSTREAM: if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir(); hasOpened=fileDialog->openSave( _("Export Command Stream"), {_("binary file"), "*.bin"}, workingDirROMExport, dpiScale, (settings.autoFillSave)?shortName:"" ); break; case GUI_FILE_EXPORT_ROM: if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir(); if (romMultiFile) { hasOpened=fileDialog->openSelectDir( _("Export ROM"), workingDirROMExport, dpiScale ); } else { hasOpened=fileDialog->openSave( _("Export ROM"), {romFilterName, "*"+romFilterExt}, workingDirROMExport, dpiScale, (settings.autoFillSave)?shortName:"" ); } break; case GUI_FILE_LOAD_MAIN_FONT: if (!dirExists(workingDirFont)) workingDirFont=getHomeDir(); hasOpened=fileDialog->openLoad( _("Select Font"), {_("compatible files"), "*.ttf *.otf *.ttc *.dfont *.pcf *.psf"}, workingDirFont, dpiScale ); break; case GUI_FILE_LOAD_HEAD_FONT: if (!dirExists(workingDirFont)) workingDirFont=getHomeDir(); hasOpened=fileDialog->openLoad( _("Select Font"), {_("compatible files"), "*.ttf *.otf *.ttc *.dfont *.pcf *.psf"}, workingDirFont, dpiScale ); break; case GUI_FILE_LOAD_PAT_FONT: if (!dirExists(workingDirFont)) workingDirFont=getHomeDir(); hasOpened=fileDialog->openLoad( _("Select Font"), {_("compatible files"), "*.ttf *.otf *.ttc *.dfont *.pcf *.psf"}, 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(workingDirLayout)) workingDirLayout=getHomeDir(); hasOpened=fileDialog->openLoad( _("Select Layout File"), {_(".ini files"), "*.ini"}, workingDirLayout, dpiScale ); break; case GUI_FILE_IMPORT_USER_PRESETS: case GUI_FILE_IMPORT_USER_PRESETS_REPLACE: if (!dirExists(workingDirConfig)) workingDirConfig=getHomeDir(); hasOpened=fileDialog->openLoad( _("Select User Presets File"), {_("configuration files"), "*.cfgu"}, workingDirConfig, dpiScale ); break; case GUI_FILE_IMPORT_CONFIG: if (!dirExists(workingDirConfig)) workingDirConfig=getHomeDir(); hasOpened=fileDialog->openLoad( _("Select Settings File"), {_("configuration files"), "*.cfg"}, workingDirConfig, 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(workingDirLayout)) workingDirLayout=getHomeDir(); hasOpened=fileDialog->openSave( _("Export Layout"), {_(".ini files"), "*.ini"}, workingDirLayout, dpiScale ); break; case GUI_FILE_EXPORT_USER_PRESETS: if (!dirExists(workingDirConfig)) workingDirConfig=getHomeDir(); hasOpened=fileDialog->openSave( _("Export User Presets"), {_("configuration files"), "*.cfgu"}, workingDirConfig, dpiScale ); break; case GUI_FILE_EXPORT_CONFIG: if (!dirExists(workingDirConfig)) workingDirConfig=getHomeDir(); hasOpened=fileDialog->openSave( _("Export Settings"), {_("configuration files"), "*.cfg"}, workingDirConfig, 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,path.c_str())) { 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(); updateROMExportAvail(); updateScroll(0); if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } pushRecentFile(path); // walk song e->walkSong(loopOrder,loopRow,loopEnd); // do not auto-play a backup if (path.find(backupPath)!=0) { if (settings.playOnLoad==2 || (settings.playOnLoad==1 && wasPlaying)) { play(); } } else { // warn the user showWarning(_("you have loaded a backup!\nif you need to, please save it somewhere.\n\nDO NOT RELY ON THE BACKUP SYSTEM FOR AUTO-SAVE!\nFurnace will not save backups of backups."),GUI_WARN_GENERIC); } // if this is a PC module import, warn the user on the first import. if (!tutorial.importedMOD && e->song.version==DIV_VERSION_MOD) { showWarning(_("you have imported a ProTracker/SoundTracker/PC module!\nkeep the following in mind:\n\n- Furnace is not a replacement for your MOD player\n- import is not perfect. your song may sound different:\n - E6x pattern loop is not supported\n\nhave fun!"),GUI_WARN_IMPORT); } if (!tutorial.importedS3M && e->song.version==DIV_VERSION_S3M) { showWarning(_("you have imported a Scream Tracker 3 module!\nkeep the following in mind:\n\n- Furnace is not a replacement for your S3M player\n- import is not perfect. your song may sound different:\n - OPL instruments may be detuned\n\nhave fun!"),GUI_WARN_IMPORT); } if (!tutorial.importedXM && e->song.version==DIV_VERSION_XM) { showWarning(_("you have imported a FastTracker II module!\nkeep the following in mind:\n\n- Furnace is not a replacement for your XM player\n- import is not perfect. your song may sound different:\n - envelopes have been converted to macros\n - global volume changes are not supported\n\nhave fun!"),GUI_WARN_IMPORT); } if (!tutorial.importedIT && e->song.version==DIV_VERSION_IT) { showWarning(_("you have imported an Impulse Tracker module!\nkeep the following in mind:\n\n- Furnace is not a replacement for your IT player\n- import is not perfect. your song may sound different:\n - envelopes have been converted to macros\n - global volume changes are not supported\n - channel volume changes are not supported\n - New Note Actions (NNA) are not supported\n\nhave fun!"),GUI_WARN_IMPORT); } return 0; } void FurnaceGUI::openRecentFile(String path) { if (modified) { nextFile=path; showWarning(_("Unsaved changes! Save changes before opening file?"),GUI_WARN_OPEN_DROP); } else { if (load(path)>0) { showError(fmt::sprintf(_("Error while loading file! (%s)"),lastError)); } } } 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(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())-settings.backupMaxCopies; 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) { songOrdersLengths.clear(); int loopOrder=0; int loopRow=0; int loopEnd=0; e->walkSong(loopOrder,loopRow,loopEnd); e->findSongLength(loopOrder,loopRow,audioExportOptions.fadeOut,songFadeoutSectionLength,songHasSongEndCommand,songOrdersLengths,songLength); // for progress estimation songLoopedSectionLength=songLength; for (int i=0; isaveAudio(path.c_str(),audioExportOptions); totalFiles=0; e->getTotalAudioFiles(totalFiles); int totalLoops=0; lengthOfOneFile=songLength; if (!songHasSongEndCommand) { e->getTotalLoops(totalLoops); lengthOfOneFile+=songLoopedSectionLength*totalLoops; lengthOfOneFile+=songFadeoutSectionLength; // account for fadeout } totalLength=lengthOfOneFile*totalFiles; curProgress=0.0f; displayExporting=true; } void FurnaceGUI::exportCmdStream(bool target, String path) { csExportPath=path; csExportTarget=target; csExportDone=false; csExportThread=new std::thread([this]() { SafeWriter* w=e->saveCommand(&csProgress,csExportOptions); csExportResult=w; csExportDone=true; }); displayExportingCS=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])&((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=(waveDragMax+1)-((dragY-waveDragStart.y)*(double((waveDragMax+1)-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()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 (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,12)) { 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 (ImGui::BeginMenu(_("gradient/fade..."))) { if (ImGui::InputInt(_("Start"),&fadeMin,1,16)) { if (fadeMin<0) fadeMin=0; if (fadeMode) { if (fadeMin>15) fadeMin=15; } else { if (fadeMin>255) fadeMin=255; } } if (ImGui::InputInt(_("End"),&fadeMax,1,16)) { 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,10,"%.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,16)) { 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,16)) { 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; } } if (selStart.xFine>2 || selEnd.xFine>2 || selStart.xCoarse!=selEnd.xCoarse) { ImGui::Checkbox(_("Set effect"),&randomizeEffect); if (randomizeEffect) { if (ImGui::InputScalar(_("Effect"),ImGuiDataType_S32,&randomizeEffectVal,&_ONE,&_SIXTEEN,"%.2X",ImGuiInputTextFlags_CharsHexadecimal)) { if (randomizeEffectVal<0) randomizeEffectVal=0; if (randomizeEffectVal>255) randomizeEffectVal=255; } } } if (ImGui::Button(_("Randomize"))) { doRandomize(randomizeMin,randomizeMax,randomMode,randomizeEffect,randomizeEffectVal); 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,4)) { 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 (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; ImGui::GetIO().AlwaysScrollText=true; 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; ImGui::GetIO().AlwaysScrollText=false; 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->getConfObject()); e->saveConf(); } #endif if (cvOpen) return 1; 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(curSample); } 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 && (!mobileUI || mobilePatSel)) { // detect whether we have to scroll if (ypatWindowPos.y+patWindowSize.y-2.0f*dpiScale) { addScroll(1); } if (xpatWindowPos.x+patWindowSize.x-(mobileUI?40.0f:4.0f)*dpiScale) { addScrollX(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) \ uint64_t __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) #define IMPORT_CLOSE(x) \ if (x) pendingLayoutImportReopen.push(&x); \ x=false; 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) #ifndef NO_INTRO DECLARE_METRIC(intro) #endif 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(xyOsc) DECLARE_METRIC(volMeter) DECLARE_METRIC(settings) DECLARE_METRIC(debug) DECLARE_METRIC(csPlayer) DECLARE_METRIC(stats) DECLARE_METRIC(memory) 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(userPresets) 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"); } if (safeMode) { showError(_("Furnace has been started in Safe Mode.\nthis means that:\n\n- software rendering is being used\n- audio output may not work\n- font loading is disabled\n\ncheck any settings which may have made Furnace start up in this mode.\nfont loading is one of these.")); settingsOpen=true; } 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); insEditMayBeDirty=true; break; case SDL_MOUSEBUTTONDOWN: pointDown(ev.button.x,ev.button.y,ev.button.button); insEditMayBeDirty=true; break; case SDL_MOUSEWHEEL: wheelX+=ev.wheel.x; wheelY+=ev.wheel.y; insEditMayBeDirty=true; 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 || (ImGuiFileDialog::Instance()->IsOpened() && !ImGui::GetIO().WantTextInput)) { keyDown(ev); } if (introPos<11.0 && !shortIntro) { if (ev.key.keysym.scancode==SDL_SCANCODE_SPACE || ev.key.keysym.scancode==SDL_SCANCODE_ESCAPE || ev.key.keysym.scancode==SDL_SCANCODE_RETURN) { introSkip=0.5; } introSkipDo=true; } insEditMayBeDirty=true; #ifdef IS_MOBILE injectBackUp=true; #endif break; case SDL_KEYUP: // for now insEditMayBeDirty=true; if (introPos<11.0 && introSkip<0.5 && !shortIntro) { introSkipDo=false; } break; case SDL_DROPFILE: if (ev.drop.file!=NULL) { if (introPos<11.0) { SDL_free(ev.drop.file); break; } int sampleCountBefore=e->song.sampleLen; std::vector instruments=e->instrumentFromFile(ev.drop.file,true,settings.readInsNames); std::vector samples = e->sampleFromFile(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); } int instrumentCount=-1; for (DivInstrument* i: instruments) { instrumentCount=e->addInstrumentPtr(i); } if (instrumentCount>=0 && settings.selectAssetOnLoad) { curIns=instrumentCount-1; } nextWindow=GUI_WINDOW_INS_LIST; MARK_MODIFIED; } else if ((droppedWave=e->waveFromFile(ev.drop.file,false))!=NULL) { int waveCount=-1; waveCount=e->addWavePtr(droppedWave); if (waveCount>=0 && settings.selectAssetOnLoad) { curWave=waveCount-1; } nextWindow=GUI_WINDOW_WAVE_LIST; MARK_MODIFIED; } else if (!samples.empty()) { if (e->song.sampleLen!=sampleCountBefore) { //e->renderSamplesP(); } if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } int sampleCount=-1; for (DivSample* s: samples) { sampleCount=e->addSamplePtr(s); } //sampleCount=e->addSamplePtr(droppedSample); if (sampleCount>=0 && settings.selectAssetOnLoad) { curSample=sampleCount; updateSampleTex=true; } 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_USEREVENT: // used for MIDI wake up break; case SDL_QUIT: if (requestQuit()) { 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; } if (rend!=NULL) { logV("restoring swap interval..."); rend->setSwapInterval(settings.vsync); } } // 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(); midiWakeUp=true; 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?msg.data[1]:-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 && !(midiMap.directChannel && midiMap.directProgram)) { 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; } #ifndef NO_INTRO if (firstFrame && !safeMode && renderBackend!=GUI_BACKEND_SOFTWARE) { 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); } } #endif if (!e->isRunning()) { activeNotes.clear(); memset(chanOscVol,0,DIV_MAX_CHANS*sizeof(float)); for (int i=0; isynchronized([this]() { for (int i=0; igetTotalChannelCount(); i++) { DivDispatchOscBuffer* buf=e->getOscBuffer(i); if (buf!=NULL) { //buf->needle=0; //buf->readNeedle=0; // TODO: should we reset here? } } }); } // 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(e->getConfObject()); rend->quitGUI(); rend->quit(); ImGui_ImplSDL2_Shutdown(); int initAttempts=0; SDL_Delay(500); logD("starting render backend..."); while (++initAttempts<=5) { if (rend->init(sdlWin,settings.vsync)) { 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 (rend->areTexturesSquare()) { ImGui::GetIO().Fonts->Flags|=ImFontAtlasFlags_Square; } firstFrame=true; mustClear=2; initialScreenWipe=1.0f; continue; } bool fontsFailed=false; layoutTimeBegin=SDL_GetPerformanceCounter(); if (pendingLayoutImport!=NULL) { if (pendingLayoutImportStep==0) { IMPORT_CLOSE(editControlsOpen); IMPORT_CLOSE(ordersOpen); IMPORT_CLOSE(insListOpen); IMPORT_CLOSE(songInfoOpen); IMPORT_CLOSE(patternOpen); IMPORT_CLOSE(insEditOpen); IMPORT_CLOSE(waveListOpen); IMPORT_CLOSE(waveEditOpen); IMPORT_CLOSE(sampleListOpen); IMPORT_CLOSE(sampleEditOpen); IMPORT_CLOSE(aboutOpen); IMPORT_CLOSE(settingsOpen); IMPORT_CLOSE(mixerOpen); IMPORT_CLOSE(debugOpen); IMPORT_CLOSE(inspectorOpen); IMPORT_CLOSE(oscOpen); IMPORT_CLOSE(volMeterOpen); IMPORT_CLOSE(statsOpen); IMPORT_CLOSE(compatFlagsOpen); IMPORT_CLOSE(pianoOpen); IMPORT_CLOSE(notesOpen); IMPORT_CLOSE(channelsOpen); IMPORT_CLOSE(regViewOpen); IMPORT_CLOSE(logOpen); IMPORT_CLOSE(effectListOpen); IMPORT_CLOSE(chanOscOpen); IMPORT_CLOSE(subSongsOpen); IMPORT_CLOSE(findOpen); IMPORT_CLOSE(spoilerOpen); IMPORT_CLOSE(patManagerOpen); IMPORT_CLOSE(sysManagerOpen); IMPORT_CLOSE(clockOpen); IMPORT_CLOSE(speedOpen); IMPORT_CLOSE(groovesOpen); IMPORT_CLOSE(xyOscOpen); IMPORT_CLOSE(memoryOpen); IMPORT_CLOSE(csPlayerOpen); IMPORT_CLOSE(userPresetsOpen); } else if (pendingLayoutImportStep==1) { // let the UI settle } else if (pendingLayoutImportStep==2) { ImGui::LoadIniSettingsFromMemory((const char*)pendingLayoutImport,pendingLayoutImportLen); } else if (pendingLayoutImportStep==3) { // restore open windows while (!pendingLayoutImportReopen.empty()) { bool* next=pendingLayoutImportReopen.front(); *next=true; pendingLayoutImportReopen.pop(); } } else if (pendingLayoutImportStep==4) { delete[] pendingLayoutImport; pendingLayoutImport=NULL; } pendingLayoutImportStep++; if (pendingLayoutImport==NULL) pendingLayoutImportStep=0; } rend->newFrame(); ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); // one second counter secondTimer+=ImGui::GetIO().DeltaTime; if (secondTimer>=1.0f) secondTimer=fmod(secondTimer,1.0f); curWindowLast=curWindow; curWindow=GUI_WINDOW_NOTHING; editOptsVisible=false; int nextPlayOrder=0; int nextOldRow=0; e->getPlayPos(nextPlayOrder,nextOldRow); oldRowChanged=false; playOrder=nextPlayOrder; if (followPattern && (!e->isStepping() || pendingStepUpdate)) { curOrder=playOrder; } if (e->isPlaying()) { if (oldRow!=nextOldRow) oldRowChanged=true; oldRow=nextOldRow; } // check whether pattern of channel(s) at cursor/selection is/are unique isPatUnique=true; if (curOrder>=0 && curOrdercurSubSong->ordersLen && selStart.xCoarse>=0 && selStart.xCoarsegetTotalChannelCount() && selEnd.xCoarse>=0 && selEnd.xCoarsegetTotalChannelCount()) { for (int i=0; icurSubSong->ordersLen; i++) { if (i==curOrder) continue; for (int j=selStart.xCoarse; j<=selEnd.xCoarse; j++) { if (e->curSubSong->orders.ord[j][i]==e->curSubSong->orders.ord[j][curOrder]) isPatUnique=false; break; } if (!isPatUnique) break; } } 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(i); i--; } openRecentFile(item); } } 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); } ImGui::Separator(); if (settings.exportOptionsLayout==0) { if (ImGui::BeginMenu(_("export audio..."))) { drawExportAudio(); ImGui::EndMenu(); } if (ImGui::BeginMenu(_("export VGM..."))) { drawExportVGM(); ImGui::EndMenu(); } if (romExportExists) { if (ImGui::BeginMenu(_("export ROM..."))) { drawExportROM(); ImGui::EndMenu(); } } if (ImGui::BeginMenu(_("export text..."))) { drawExportText(); ImGui::EndMenu(); } if (ImGui::BeginMenu(_("export command stream..."))) { drawExportCommand(); ImGui::EndMenu(); } if (ImGui::BeginMenu(_("export .dmf..."))) { drawExportDMF(); ImGui::EndMenu(); } } else if (settings.exportOptionsLayout==2) { if (ImGui::MenuItem(_("export audio..."))) { curExportType=GUI_EXPORT_AUDIO; displayExport=true; } if (ImGui::MenuItem(_("export VGM..."))) { curExportType=GUI_EXPORT_VGM; displayExport=true; } if (romExportExists) { if (ImGui::MenuItem(_("export ROM..."))) { curExportType=GUI_EXPORT_ROM; displayExport=true; } } if (ImGui::MenuItem(_("export text..."))) { curExportType=GUI_EXPORT_TEXT; displayExport=true; } if (ImGui::MenuItem(_("export command stream..."))) { curExportType=GUI_EXPORT_CMD_STREAM; displayExport=true; } if (ImGui::MenuItem(_("export .dmf..."))) { curExportType=GUI_EXPORT_DMF; displayExport=true; } } else { if (ImGui::MenuItem(_("export..."),BIND_FOR(GUI_ACTION_EXPORT))) { displayExport=true; } } 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(false); if (picked!=DIV_SYSTEM_NULL) { if (!e->addSystem(picked)) { showError(fmt::sprintf(_("cannot add chip! (%s)"),e->getLastError())); } else { MARK_MODIFIED; } ImGui::CloseCurrentPopup(); if (e->song.autoSystem) { autoDetectSystem(); } updateWindowTitle(); updateROMExportAvail(); } 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,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(false); if (picked!=DIV_SYSTEM_NULL) { if (e->changeSystem(i,picked,preserveChanPos)) { MARK_MODIFIED; if (e->song.autoSystem) { autoDetectSystem(); } updateWindowTitle(); updateROMExportAvail(); } else { showError(fmt::sprintf(_("cannot change chip! (%s)"),e->getLastError())); } 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(fmt::sprintf(_("cannot remove chip! (%s)"),e->getLastError())); } else { MARK_MODIFIED; } if (e->song.autoSystem) { autoDetectSystem(); updateWindowTitle(); } updateROMExportAvail(); } } ImGui::EndMenu(); } } #if defined(FURNACE_DATADIR) && defined(SHOW_OPEN_ASSETS_MENU_ENTRY) if (ImGui::MenuItem(_("open built-in assets directory"))) { SDL_OpenURL("file://" FURNACE_DATADIR); } #endif 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..."),BIND_FOR(GUI_ACTION_QUIT))) { requestQuit(); } 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(_("pattern 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(_("user systems..."),BIND_FOR(GUI_ACTION_WINDOW_USER_PRESETS))) { userPresetsOpen=true; } if (ImGui::MenuItem(_("settings..."),BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) { syncSettings(); settingsOpen=true; } ImGui::EndMenu(); } if (ImGui::BeginMenu(settings.capitalMenuBar?_("Window"):_("window"))) { if (ImGui::BeginMenu(_("song"))) { if (ImGui::MenuItem(_("song comments"), BIND_FOR(GUI_ACTION_WINDOW_NOTES), notesOpen)) notesOpen = !notesOpen; 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; ImGui::Separator(); if (ImGui::MenuItem(_("channels"),BIND_FOR(GUI_ACTION_WINDOW_CHANNELS),channelsOpen)) channelsOpen=!channelsOpen; if (ImGui::MenuItem(_("chip manager"),BIND_FOR(GUI_ACTION_WINDOW_SYS_MANAGER),sysManagerOpen)) sysManagerOpen=!sysManagerOpen; 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(_("pattern manager"),BIND_FOR(GUI_ACTION_WINDOW_PAT_MANAGER),patManagerOpen)) patManagerOpen=!patManagerOpen; if (ImGui::MenuItem(_("mixer"),BIND_FOR(GUI_ACTION_WINDOW_MIXER),mixerOpen)) mixerOpen=!mixerOpen; if (ImGui::MenuItem(_("compatibility flags"),BIND_FOR(GUI_ACTION_WINDOW_COMPAT_FLAGS),compatFlagsOpen)) compatFlagsOpen=!compatFlagsOpen; ImGui::EndMenu(); } if (ImGui::BeginMenu(_("assets"))) { 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(_("samples"), BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_LIST), sampleListOpen)) sampleListOpen = !sampleListOpen; if (ImGui::MenuItem(_("wavetables"), BIND_FOR(GUI_ACTION_WINDOW_WAVE_LIST), waveListOpen)) waveListOpen = !waveListOpen; } ImGui::Separator(); if (ImGui::MenuItem(_("instrument editor"), BIND_FOR(GUI_ACTION_WINDOW_INS_EDIT), insEditOpen)) insEditOpen = !insEditOpen; if (ImGui::MenuItem(_("sample editor"), BIND_FOR(GUI_ACTION_WINDOW_SAMPLE_EDIT), sampleEditOpen)) sampleEditOpen = !sampleEditOpen; if (ImGui::MenuItem(_("wavetable editor"), BIND_FOR(GUI_ACTION_WINDOW_WAVE_EDIT), waveEditOpen)) waveEditOpen = !waveEditOpen; ImGui::EndMenu(); } if (ImGui::BeginMenu(_("visualizers"))) { 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(_("oscilloscope (X-Y)"),BIND_FOR(GUI_ACTION_WINDOW_XY_OSC),xyOscOpen)) xyOscOpen=!xyOscOpen; if (ImGui::MenuItem(_("volume meter"),BIND_FOR(GUI_ACTION_WINDOW_VOL_METER),volMeterOpen)) volMeterOpen=!volMeterOpen; ImGui::EndMenu(); } if (ImGui::BeginMenu(_("tempo"))) { if (ImGui::MenuItem(_("clock"),BIND_FOR(GUI_ACTION_WINDOW_CLOCK),clockOpen)) clockOpen=!clockOpen; if (ImGui::MenuItem(_("grooves"),BIND_FOR(GUI_ACTION_WINDOW_GROOVES),groovesOpen)) groovesOpen=!groovesOpen; if (ImGui::MenuItem(_("speed"),BIND_FOR(GUI_ACTION_WINDOW_SPEED),speedOpen)) speedOpen=!speedOpen; ImGui::EndMenu(); } if (ImGui::BeginMenu(_("debug"))) { if (ImGui::MenuItem(_("log viewer"),BIND_FOR(GUI_ACTION_WINDOW_LOG),logOpen)) logOpen=!logOpen; if (ImGui::MenuItem(_("register view"),BIND_FOR(GUI_ACTION_WINDOW_REGISTER_VIEW),regViewOpen)) regViewOpen=!regViewOpen; if (ImGui::MenuItem(_("statistics"),BIND_FOR(GUI_ACTION_WINDOW_STATS),statsOpen)) statsOpen=!statsOpen; if (ImGui::MenuItem(_("memory composition"),BIND_FOR(GUI_ACTION_WINDOW_MEMORY),memoryOpen)) memoryOpen=!memoryOpen; if (ImGui::MenuItem(_("command stream player"),BIND_FOR(GUI_ACTION_WINDOW_CS_PLAYER),csPlayerOpen)) csPlayerOpen=!csPlayerOpen; ImGui::EndMenu(); } ImGui::Separator(); if (ImGui::MenuItem(_("effect list"),BIND_FOR(GUI_ACTION_WINDOW_EFFECT_LIST),effectListOpen)) effectListOpen=!effectListOpen; 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 (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(_("welcome screen"))) tutorial.protoWelcome=false; 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() && settings.playbackTime) { 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) "),e->getCurHz(),calcBPM(e->getSpeeds(),e->getCurHz(),e->getVirtualTempoN(),e->getVirtualTempoD())); if (settings.orderRowsBase) { info+=fmt::sprintf(_("| Order %.2X/%.2X "),playOrder,e->curSubSong->ordersLen-1); } else { info+=fmt::sprintf(_("| Order %d/%d "),playOrder,e->curSubSong->ordersLen-1); } if (settings.patRowsBase) { info+=fmt::sprintf(_("| Row %.2X/%.2X "),oldRow,e->curSubSong->patLen); } else { info+=fmt::sprintf(_("| Row %d/%d "),oldRow,e->curSubSong->patLen); } info+=_("| "); if (totalSeconds==0x7fffffff) { info+=_("Don't you have anything better to do?"); } else { if (totalSeconds>=86400) { int totalDays=totalSeconds/86400; int totalYears=totalDays/365; totalDays%=365; int totalMonths=totalDays/30; totalDays%=30; #ifdef HAVE_LOCALE info+=fmt::sprintf(ngettext("%d year ","%d years ",totalYears),totalYears); info+=fmt::sprintf(ngettext("%d month ","%d months ",totalMonths),totalMonths); info+=fmt::sprintf(ngettext("%d day ","%d days ",totalDays),totalDays); #else info+=fmt::sprintf(_GN("%d year ","%d years ",totalYears),totalYears); info+=fmt::sprintf(_GN("%d month ","%d months ",totalMonths),totalMonths); info+=fmt::sprintf(_GN("%d day ","%d days ",totalDays),totalDays); #endif } if (totalSeconds>=3600) { info+=fmt::sprintf("%.2d:",(totalSeconds/3600)%24); } info+=fmt::sprintf("%.2d:%.2d.%.2d",(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 { float realVol=e->getGain(cursor.xCoarse,p->data[cursor.y][3]); info=fmt::sprintf(_("Set volume: %d (%.2X, %d%%)"),p->data[cursor.y][3],p->data[cursor.y][3],(int)(realVol*100.0f)); } 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 (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()); MEASURE(piano,drawPiano()); 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(csPlayer,drawCSPlayer()); MEASURE(log,drawLog()); MEASURE(compatFlags,drawCompatFlags()); MEASURE(stats,drawStats()); MEASURE(readOsc,readOsc()); MEASURE(osc,drawOsc()); MEASURE(chanOsc,drawChanOsc()); MEASURE(xyOsc,drawXYOsc()); MEASURE(volMeter,drawVolMeter()); MEASURE(grooves,drawGrooves()); MEASURE(regView,drawRegView()); MEASURE(memory,drawMemory()); MEASURE(effectList,drawEffectList()); MEASURE(userPresets,drawUserPresets()); MEASURE(patManager,drawPatManager()); } else { globalWinFlags=0; ImGui::DockSpaceOverViewport(0,NULL,lockLayout?(ImGuiDockNodeFlags_NoWindowMenuButton|ImGuiDockNodeFlags_NoMove|ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplit|ImGuiDockNodeFlags_NoDockingSplitOther):0); MEASURE(subSongs,drawSubSongs()); MEASURE(findReplace,drawFindReplace()); MEASURE(spoiler,drawSpoiler()); MEASURE(pattern,drawPattern()); MEASURE(editControls,drawEditControls()); MEASURE(speed,drawSpeed()); 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(xyOsc,drawXYOsc()); MEASURE(volMeter,drawVolMeter()); MEASURE(settings,drawSettings()); MEASURE(debug,drawDebug()); MEASURE(csPlayer,drawCSPlayer()); MEASURE(stats,drawStats()); MEASURE(memory,drawMemory()); MEASURE(compatFlags,drawCompatFlags()); MEASURE(piano,drawPiano()); MEASURE(notes,drawNotes()); MEASURE(channels,drawChannels()); MEASURE(patManager,drawPatManager()); MEASURE(sysManager,drawSysManager()); MEASURE(clock,drawClock()); MEASURE(regView,drawRegView()); MEASURE(log,drawLog()); MEASURE(effectList,drawEffectList()); MEASURE(userPresets,drawUserPresets()); } // 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.order==selStart.order && cursor.xCoarse==selEnd.xCoarse && cursor.xFine==selEnd.xFine && cursor.y==selEnd.y && cursor.order==selEnd.order) { 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; } 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 #ifndef FLATPAK_WORKAROUNDS 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 #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: case GUI_FILE_INS_SAVE_ALL: 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: case GUI_FILE_WAVE_SAVE_ALL: 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: case GUI_FILE_SAMPLE_SAVE_ALL: 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_ROM: case GUI_FILE_EXPORT_TEXT: case GUI_FILE_EXPORT_CMDSTREAM: 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_IMPORT_USER_PRESETS: case GUI_FILE_IMPORT_USER_PRESETS_REPLACE: case GUI_FILE_EXPORT_USER_PRESETS: case GUI_FILE_IMPORT_CONFIG: case GUI_FILE_EXPORT_CONFIG: workingDirConfig=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_ROM) { checkExtension(romFilterExt.c_str()); } if (curFileDialog==GUI_FILE_EXPORT_TEXT) { checkExtension(".txt"); } if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM) { 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"); } if (curFileDialog==GUI_FILE_EXPORT_USER_PRESETS) { checkExtension(".cfgu"); } if (curFileDialog==GUI_FILE_EXPORT_CONFIG) { checkExtension(".cfg"); } 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; case GUI_WARN_CV: cvOpen=true; 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(),&e->song,settings.writeInsNames)) { 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! only the following instrument types are supported:\n- FM (OPN)\n- SN76489/Sega PSG\n- Game Boy\n- PC Engine\n- NES\n- C64\n- FM (OPLL)\n- FDS")); } else { pushRecentSys(copyOfName.c_str()); } } break; case GUI_FILE_INS_SAVE_ALL: { String errors; for (int i=0; isong.insLen; i++) { String nextPath=copyOfName; nextPath+=DIR_SEPARATOR_STR; nextPath+=fmt::sprintf("%.2X_",i); for (char j: e->song.ins[i]->name) { switch (j) { // these chars are reserved case '/': case '<': case '>': case ':': case '"': case '\\': case '|': case '?': case '*': nextPath+='_'; break; default: nextPath+=j; break; } } nextPath+=".fui"; logV("%s",nextPath); if (!e->song.ins[i]->save(nextPath.c_str(),&e->song,settings.writeInsNames)) { errors+=fmt::sprintf("%s: could not save!\n",e->song.ins[i]->name); } } if (!errors.empty()) { showError(errors); } break; } case GUI_FILE_WAVE_SAVE_ALL: { String errors; for (int i=0; isong.waveLen; i++) { String nextPath=copyOfName; nextPath+=DIR_SEPARATOR_STR; nextPath+=fmt::sprintf("%.2X.fuw",i); logV("%s",nextPath); if (!e->song.wave[i]->save(nextPath.c_str())) { errors+=fmt::sprintf("%d: could not save!\n",i); } } if (!errors.empty()) { showError(errors); } break; } case GUI_FILE_SAMPLE_SAVE_ALL: { String errors; for (int i=0; isong.sampleLen; i++) { String nextPath=copyOfName; nextPath+=DIR_SEPARATOR_STR; nextPath+=fmt::sprintf("%.2X_",i); for (char j: e->song.sample[i]->name) { switch (j) { // these chars are reserved case '/': case '<': case '>': case ':': case '"': case '\\': case '|': case '?': case '*': nextPath+='_'; break; default: nextPath+=j; break; } } nextPath+=".wav"; logV("%s",nextPath); if (!e->song.sample[i]->save(nextPath.c_str())) { errors+=fmt::sprintf("%s: could not save!\n",e->song.sample[i]->name); } } if (!errors.empty()) { showError(errors); } 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 samples:\n"); bool warn=false; for (String i: fileDialog->getFileName()) { std::vector samples=e->sampleFromFile(i.c_str()); if (samples.empty()) { if (fileDialog->getFileName().size()>1) { warn=true; errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError()); } else {; showError(e->getLastError()); } } else { if((int)samples.size() == 1) { if (e->addSamplePtr(samples[0]) == -1) { if (fileDialog->getFileName().size()>1) { warn=true; errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError()); } else { showError(e->getLastError()); } } else { MARK_MODIFIED; } } else { for (DivSample* s: samples) { //ask which samples to load! pendingSamples.push_back(std::make_pair(s,false)); } displayPendingSamples=true; replacePendingSample = false; } } } if (warn) { showWarning(errs,GUI_WARN_GENERIC); } break; } case GUI_FILE_SAMPLE_OPEN_REPLACE: { std::vector samples=e->sampleFromFile(copyOfName.c_str()); if (samples.empty()) { showError(e->getLastError()); } else { if ((int)samples.size()==1) { if (curSample>=0 && curSample<(int)e->song.sample.size()) { DivSample* s=samples[0]; 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; }); updateSampleTex=true; } else { showError(_("...but you haven't selected a sample!")); delete samples[0]; } } else { for (DivSample* s: samples) { // ask which samples to load! pendingSamples.push_back(std::make_pair(s,false)); } displayPendingSamples=true; replacePendingSample=true; } } 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(),true,settings.readInsNames); 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(fmt::sprintf(_("cannot load instrument! (%s)"),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 int instrumentCount=-1; for (DivInstrument* i: instruments) { instrumentCount=e->addInstrumentPtr(i); MARK_MODIFIED; } if (instrumentCount>=0 && settings.selectAssetOnLoad) { curIns=instrumentCount-1; } } } break; } case GUI_FILE_INS_OPEN_REPLACE: { int sampleCountBefore=e->song.sampleLen; std::vector instruments=e->instrumentFromFile(copyOfName.c_str(),true,settings.readInsNames); 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]; // reset macro zoom memset(e->song.ins[curIns]->temp.vZoom,-1,sizeof(e->song.ins[curIns]->temp.vZoom)); MARK_MODIFIED; } else { showError(_("...but you haven't selected an instrument!")); } for (DivInstrument* i: instruments) { delete i; } } } else { showError(fmt::sprintf(_("cannot load instrument! (%s)"),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(fmt::sprintf(_("cannot load wavetable! (%s)"),e->getLastError())); } } else { int waveCount=-1; waveCount=e->addWavePtr(wave); if (waveCount==-1) { if (fileDialog->getFileName().size()>1) { warn=true; errs+=fmt::sprintf("- %s: %s\n",i,e->getLastError()); } else { showError(fmt::sprintf(_("cannot load wavetable! (%s)"),e->getLastError())); } } else { if (settings.selectAssetOnLoad) { curWave=waveCount-1; } 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(fmt::sprintf(_("cannot load wavetable! (%s)"),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,vgmExportDPCM07,vgmExportCorrectedRate); 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_ROM: romExportPath=copyOfName; pendingExport=e->buildROM(romTarget); if (pendingExport==NULL) { showError("could not create exporter! you may want to report this issue..."); } else { pendingExport->setConf(romConfig); if (pendingExport->go(e)) { displayExportingROM=true; romExportSave=true; } else { showError("could not begin exporting process! TODO: elaborate"); } } break; case GUI_FILE_EXPORT_TEXT: { SafeWriter* w=e->saveText(false); 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 text! (%s)"),e->getLastError())); } break; } case GUI_FILE_EXPORT_CMDSTREAM: { exportCmdStream(false,copyOfName); 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_IMPORT_USER_PRESETS: if (!loadUserPresets(false,copyOfName,true)) { showError(_("could not import user presets!")); } break; case GUI_FILE_IMPORT_USER_PRESETS_REPLACE: if (!loadUserPresets(false,copyOfName,false)) { showError(fmt::sprintf(_("could not import user presets! (%s)"),strerror(errno))); } break; case GUI_FILE_IMPORT_CONFIG: importConfig(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_EXPORT_USER_PRESETS: if (!saveUserPresets(false,copyOfName)) { showError(fmt::sprintf(_("could not import user presets! (%s)"),strerror(errno))); } break; case GUI_FILE_EXPORT_CONFIG: exportConfig(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 (displayPendingSamples) { displayPendingSamples=false; ImGui::OpenPopup(_("Select Sample")); } if (displayPendingRawSample) { displayPendingRawSample=false; ImGui::OpenPopup(_("Import Raw Sample")); } if (displayInsTypeList) { displayInsTypeList=false; ImGui::OpenPopup("InsTypeList"); } if (displayWaveSizeList) { displayWaveSizeList=false; ImGui::OpenPopup("WaveSizeList"); } if (displayExporting) { displayExporting=false; ImGui::OpenPopup(_("Rendering...")); } if (displayExportingROM) { displayExportingROM=false; ImGui::OpenPopup(_("ROM Export Progress")); } if (displayExportingCS) { displayExportingCS=false; ImGui::OpenPopup(_("CmdStream Export Progress")); } 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(); updateROMExportAvail(); } else { ImGui::OpenPopup(_("New Song")); } } if (displayPalette) { paletteSearchResults.clear(); paletteQuery=""; paletteFirstFrame=true; curPaletteChoice=0; displayPalette=false; ImGui::OpenPopup(_("Command Palette")); } if (displayExport) { displayExport=false; ImGui::OpenPopup(_("Export")); } 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); // ImGui::SetNextWindowSize(ImVec2(0.0f,0.0f)); if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings)) { // WHAT the HELL?! WAKE_UP; if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_CHAN) { ImGui::Text(_("Please wait...")); } float* progressLambda=&curProgress; int curPosInRows=0; int* curPosInRowsLambda=&curPosInRows; int loopsLeft=0; int* loopsLeftLambda=&loopsLeft; int totalLoops=0; int* totalLoopsLambda=&totalLoops; int curFile=0; int* curFileLambda=&curFile; if (e->isExporting()) { e->lockEngine( [this, progressLambda, curPosInRowsLambda, curFileLambda, loopsLeftLambda, totalLoopsLambda] () { int curRow=0; int curOrder=0; e->getCurSongPos(curRow, curOrder); *curFileLambda=0; e->getCurFileIndex(*curFileLambda); *curPosInRowsLambda=curRow; for (int i=0; igetLoopsLeft(*loopsLeftLambda); e->getTotalLoops(*totalLoopsLambda); if ((*totalLoopsLambda)!=(*loopsLeftLambda)) { // we are going 2nd, 3rd, etc. time through the song *curPosInRowsLambda-=(songLength-songLoopedSectionLength); // a hack so progress bar does not jump? } if (e->getIsFadingOut()) { // we are in fadeout??? why it works like that bruh // LIVE WITH IT damn it *curPosInRowsLambda-=(songLength-songLoopedSectionLength); // a hack so progress bar does not jump? } } if (totalLength<0.1) { // DON'T *progressLambda=0; } else { *progressLambda=(float)((*curPosInRowsLambda)+((*totalLoopsLambda)-(*loopsLeftLambda))*songLength+lengthOfOneFile*(*curFileLambda))/(float)totalLength; } } ); } ImGui::Text(_("Row %d of %d"),curPosInRows+((totalLoops)-(loopsLeft))*songLength,lengthOfOneFile); if (audioExportOptions.mode==DIV_EXPORT_MODE_MANY_CHAN) ImGui::Text(_("Channel %d of %d"),curFile+1,totalFiles); ImGui::ProgressBar(curProgress,ImVec2(320.0f*dpiScale,0),fmt::sprintf("%.2f%%",curProgress*100.0f).c_str()); if (ImGui::Button(_("Abort"))) { if (e->haltAudioFile()) { ImGui::CloseCurrentPopup(); } } if (!e->isExporting()) { e->finishAudioFile(); ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } ImVec2 romExportMinSize=mobileUI?ImVec2(canvasW-(portrait?0:(60.0*dpiScale)),canvasH-60.0*dpiScale):ImVec2(400.0f*dpiScale,200.0f*dpiScale); ImVec2 romExportMaxSize=ImVec2(canvasW-((mobileUI && !portrait)?(60.0*dpiScale):0),canvasH-(mobileUI?(60.0*dpiScale):0)); centerNextWindow(_("ROM Export Progress"),canvasW,canvasH); ImGui::SetNextWindowSizeConstraints(romExportMinSize,romExportMaxSize); if (ImGui::BeginPopupModal(_("ROM Export Progress"),NULL)) { if (pendingExport==NULL) { ImGui::TextWrapped("%s",_("...ooooor you could try asking me a new ROM export?")); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::Button(_("Erm what the sigma???"),ImVec2(ImGui::GetContentRegionAvail().x,0.0f))) { ImGui::CloseCurrentPopup(); } } else { int progIndex=0; while (true) { DivROMExportProgress p=pendingExport->getProgress(progIndex); if (p.name.empty()) break; ImGui::Text("%s: %d%%",p.name.c_str(),(int)round(p.amount*100.0f)); ImGui::ProgressBar(p.amount,ImVec2(-FLT_MIN,0)); progIndex++; } ImVec2 romLogSize=ImGui::GetContentRegionAvail(); romLogSize.y-=ImGui::GetFrameHeightWithSpacing(); if (romLogSize.y<60.0f*dpiScale) romLogSize.y=60.0f*dpiScale; if (ImGui::BeginChild("Export Log",romLogSize,ImGuiChildFlags_Border)) { pendingExport->logLock.lock(); ImGui::PushFont(patFont); for (String& i: pendingExport->exportLog) { ImGui::TextWrapped("%s",i.c_str()); } if (romExportSave) { ImGui::SetScrollY(ImGui::GetScrollMaxY()); } ImGui::PopFont(); pendingExport->logLock.unlock(); } ImGui::EndChild(); if (pendingExport->isRunning()) { WAKE_UP; if (ImGui::Button(_("Abort"),ImVec2(ImGui::GetContentRegionAvail().x,0.0f))) { pendingExport->abort(); delete pendingExport; pendingExport=NULL; romExportSave=false; ImGui::CloseCurrentPopup(); } } else { if (romExportSave) { pendingExport->wait(); if (!pendingExport->hasFailed()) { // save files here (romExportPath) for (DivROMExportOutput& i: pendingExport->getResult()) { String path=romExportPath; if (romMultiFile) { path+=DIR_SEPARATOR_STR; path+=i.name; } FILE* outFile=ps_fopen(path.c_str(),"wb"); if (outFile!=NULL) { fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); fclose(outFile); } else { // TODO: handle failure here } i.data->finish(); delete i.data; } } romExportSave=false; } if (pendingExport!=NULL) { if (pendingExport->hasFailed()) { ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted(_("Error!")); ImGui::SameLine(); } } ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::Button(_("OK"),ImVec2(ImGui::GetContentRegionAvail().x,0.0f))) { delete pendingExport; pendingExport=NULL; ImGui::CloseCurrentPopup(); } } } ImGui::EndPopup(); } centerNextWindow(_("CmdStream Export Progress"),canvasW,canvasH); ImGui::SetNextWindowSizeConstraints(romExportMinSize,romExportMaxSize); if (ImGui::BeginPopupModal(_("CmdStream Export Progress"),NULL)) { if (csExportThread==NULL) { ImGui::TextWrapped("%s",_("it appears your Furnace has too many bugs in it. any song you can export?")); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::Button(_("Talk With Devs"),ImVec2(ImGui::GetContentRegionAvail().x/3.0f,0.0f))) { ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button(_("Ask on Bug Report"),ImVec2(ImGui::GetContentRegionAvail().x/3.0f,0.0f))) { ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button(_("View Issues"),ImVec2(ImGui::GetContentRegionAvail().x/3.0f,0.0f))) { ImGui::CloseCurrentPopup(); } } else { WAKE_UP; ImGui::Text("Exporting..."); ImGui::Text("opt stage: %d",csProgress.optStage); ImGui::Text("pass: %d/%d",csProgress.optCurrent,csProgress.optTotal); ImGui::Text("find: %d/%d",csProgress.findCurrent,csProgress.findTotal); ImGui::Text("expand: %d/%d",csProgress.expandCurrent,csProgress.optCurrent); ImGui::Text("benefit: %d/%d",csProgress.origCurrent,csProgress.origCount); // check whether we're done if (csExportDone) { csExportThread->join(); delete csExportThread; csExportThread=NULL; if (csExportTarget) { // command stream player if (csExportResult!=NULL) { if (!e->playStream(csExportResult->getFinalBuf(),csExportResult->size())) { showError(e->getLastError()); csExportResult->finish(); delete csExportResult; } else { csExportResult->disown(); delete csExportResult; } } else { showError(_("oh no! it broke!")); } csExportResult=NULL; } else { // command stream export if (csExportResult!=NULL) { FILE* f=ps_fopen(csExportPath.c_str(),"wb"); if (f!=NULL) { fwrite(csExportResult->getFinalBuf(),1,csExportResult->size(),f); fclose(f); pushRecentSys(csExportPath.c_str()); } else { showError(_("could not open file!")); } csExportResult->finish(); delete csExportResult; if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } } else { showError(fmt::sprintf(_("could not write command stream! (%s)"),e->getLastError())); } csExportResult=NULL; } 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_CV: if (ImGui::Button(_("Yes"))) { ImGui::CloseCurrentPopup(); if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); postWarnAction=GUI_WARN_CV; } else { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { showError(fmt::sprintf(_("Error while saving file! (%s)"),lastError)); } else { cvOpen=true; } } } ImGui::SameLine(); if (ImGui::Button(_("No"))) { ImGui::CloseCurrentPopup(); cvOpen=true; } 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))); } } settingsChanged=true; } ImGui::SameLine(); if (ImGui::Button(_("No"))) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_RESET_KEYBINDS: if (ImGui::Button(_("Yes"))) { ImGui::CloseCurrentPopup(); resetKeybinds(); settingsChanged=true; } ImGui::SameLine(); if (ImGui::Button(_("No"))) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_RESET_COLORS: if (ImGui::Button(_("Yes"))) { ImGui::CloseCurrentPopup(); resetColors(); applyUISettings(false); settingsChanged=true; } ImGui::SameLine(); if (ImGui::Button(_("No"))) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_CLOSE_SETTINGS: if (ImGui::Button(_("Yes"))) { ImGui::CloseCurrentPopup(); settingsOpen=false; willCommit=true; settingsChanged=false; } ImGui::SameLine(); if (ImGui::Button(_("No"))) { ImGui::CloseCurrentPopup(); settingsOpen=false; syncSettings(); settingsChanged=false; } ImGui::SameLine(); if (ImGui::Button(_("Cancel")) || ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_CLEAR: if (ImGui::BeginTable("EraseOpt",2,ImGuiTableFlags_BordersInnerV)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.5f); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.5f); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::PushFont(headFont); ImGui::AlignTextToFramePadding(); ImGui::Text(_("Erasing")); ImGui::PopFont(); if (ImGui::Button(_("All subsongs"))) { stop(); e->clearSubSongs(); curOrder=0; cursor.order=0; selStart.order=0; selEnd.order=0; MARK_MODIFIED; ImGui::CloseCurrentPopup(); } if (ImGui::Button(_("Current subsong"))) { stop(); e->lockEngine([this]() { e->curSubSong->clearData(); }); e->setOrder(0); curOrder=0; cursor.order=0; selStart.order=0; selEnd.order=0; MARK_MODIFIED; ImGui::CloseCurrentPopup(); } 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; cursor.order=0; selStart.order=0; selEnd.order=0; MARK_MODIFIED; ImGui::CloseCurrentPopup(); } 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(); } if (ImGui::Button(_("Instruments"))) { stop(); e->lockEngine([this]() { e->song.clearInstruments(); }); curIns=-1; MARK_MODIFIED; ImGui::CloseCurrentPopup(); } if (ImGui::Button(_("Wavetables"))) { stop(); e->lockEngine([this]() { e->song.clearWavetables(); }); curWave=0; MARK_MODIFIED; ImGui::CloseCurrentPopup(); } if (ImGui::Button(_("Samples"))) { stop(); e->lockEngine([this]() { e->song.clearSamples(); }); curSample=0; MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::TableNextColumn(); ImGui::PushFont(headFont); ImGui::AlignTextToFramePadding(); ImGui::Text(_("Optimization")); ImGui::PopFont(); if (ImGui::Button(_("De-duplicate patterns"))) { stop(); e->lockEngine([this]() { e->curSubSong->optimizePatterns(); e->curSubSong->rearrangePatterns(); }); MARK_MODIFIED; ImGui::CloseCurrentPopup(); } if (ImGui::Button(_("Remove unused instruments"))) { stop(); e->delUnusedIns(); MARK_MODIFIED; ImGui::CloseCurrentPopup(); } /* if (ImGui::Button(_("Remove unused wavetables"))) { stop(); e->delUnusedWaves(); MARK_MODIFIED; ImGui::CloseCurrentPopup(); }*/ if (ImGui::Button(_("Remove unused samples"))) { stop(); e->delUnusedSamples(); MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::EndTable(); } if (ImGui::BeginTable("EraseOptFooter",3)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.5f); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.5f); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TableNextColumn(); if (ImGui::Button(_("Never mind! Cancel")) || ImGui::IsKeyPressed(ImGuiKey_Escape)) { ImGui::CloseCurrentPopup(); } ImGui::TableNextColumn(); ImGui::EndTable(); } break; case GUI_WARN_SUBSONG_DEL: if (ImGui::Button(_("Yes"))) { if (e->removeSubSong(e->getCurrentSubSong())) { undoHist.clear(); redoHist.clear(); updateScroll(0); oldRow=0; cursor.xCoarse=0; cursor.xFine=0; cursor.y=0; cursor.order=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; } updateROMExportAvail(); 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_RESET_CONFIG: pushDestColor(); if (ImGui::Button(_("Yes"))) { e->factoryReset(); quit=true; quitNoSave=true; ImGui::CloseCurrentPopup(); } popDestColor(); ImGui::SameLine(); if (ImGui::Button(_("No"))) { ImGui::CloseCurrentPopup(); } break; case GUI_WARN_IMPORT: if (ImGui::Button(_("Got it"))) { switch (e->song.version) { case DIV_VERSION_MOD: tutorial.importedMOD=true; break; case DIV_VERSION_S3M: tutorial.importedS3M=true; break; case DIV_VERSION_XM: tutorial.importedXM=true; break; case DIV_VERSION_IT: tutorial.importedIT=true; break; } commitTutorial(); 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]; if (displayInsTypeListMakeInsSample==-2) { ImGui::Text(_("Drum kit mode:")); if (ImGui::RadioButton(_("Normal"),!makeDrumkitMode)) { makeDrumkitMode=false; } if (ImGui::RadioButton(_("12 samples per octave"),makeDrumkitMode)) { makeDrumkitMode=true; } if (!makeDrumkitMode) { ImGui::Text(_("Starting octave")); ImGui::SameLine(); if (ImGui::InputInt("##DKOctave",&makeDrumkitOctave,1,3)) { if (makeDrumkitOctave<0) makeDrumkitOctave=0; if (makeDrumkitOctave>9) makeDrumkitOctave=9; } } ImGui::Separator(); } 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==-2) { e->song.ins[curIns]->type=i; e->song.ins[curIns]->name=_("Drum Kit"); e->song.ins[curIns]->amiga.useNoteMap=true; if (i!=DIV_INS_AMIGA) e->song.ins[curIns]->amiga.useSample=true; if (makeDrumkitMode) { for (int j=0; j<120; j++) { e->song.ins[curIns]->amiga.noteMap[j].freq=48; e->song.ins[curIns]->amiga.noteMap[j].dpcmFreq=15; e->song.ins[curIns]->amiga.noteMap[j].map=j%12; if ((j%12)>=e->song.sampleLen) continue; } } else { int index=-makeDrumkitOctave*12; for (int j=0; j<120; j++) { e->song.ins[curIns]->amiga.noteMap[j].freq=48; e->song.ins[curIns]->amiga.noteMap[j].dpcmFreq=15; if (index<0 || index>=e->song.sampleLen) { index++; continue; } e->song.ins[curIns]->amiga.noteMap[j].map=index++; } } nextWindow=GUI_WINDOW_INS_EDIT; wavePreviewInit=true; updateFMPreview=true; } 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; e->song.ins[curIns]->esfm.op[i].ct=0; e->song.ins[curIns]->esfm.op[i].dt=0; e->song.ins[curIns]->esfm.op[i].modIn=0; e->song.ins[curIns]->esfm.op[i].outLvl=0; } } MARK_MODIFIED; } } } ImGui::EndPopup(); } if (ImGui::BeginPopup("WaveSizeList",ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) { char temp[1024]; for (FurnaceGUIWaveSizeEntry i: waveSizeList) { snprintf(temp,1023,"%d×%d (%s)",i.width,i.height,i.sys); if (ImGui::MenuItem(temp)) { // create wave curWave=e->addWave(); if (curWave==-1) { showError(_("too many wavetables!")); } else { e->song.wave[curWave]->len=i.width; e->song.wave[curWave]->max=i.height-1; for (int j=0; jsong.wave[curWave]->data[j]=(j*i.height)/i.width; } MARK_MODIFIED; RESET_WAVE_MACRO_ZOOM; } } } 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; // reset macro zoom memset(e->song.ins[curIns]->temp.vZoom,-1,sizeof(e->song.ins[curIns]->temp.vZoom)); } else { showError(_("...but you haven't selected an instrument!")); } } delete i.first; } else { e->addInstrumentPtr(i.first); } } pendingIns.clear(); } ImGui::EndPopup(); } // TODO: fix style centerNextWindow(_("Select Sample"),canvasW,canvasH); if (ImGui::BeginPopupModal(_("Select Sample"),NULL,ImGuiWindowFlags_AlwaysAutoResize)) { bool quitPlease=false; ImGui::AlignTextToFramePadding(); ImGui::Text(_("this is a sample bank! select which ones to load:")); ImGui::SameLine(); if (ImGui::Button(_("All"))) { for (std::pair& i: pendingSamples) { i.second=true; } } ImGui::SameLine(); if (ImGui::Button(_("None"))) { for (std::pair& i: pendingSamples) { i.second=false; } } bool reissueSearch=false; bool anySelected=false; float sizeY=ImGui::GetFrameHeightWithSpacing()*pendingSamples.size(); if (sizeY>(canvasH-180.0*dpiScale)) { sizeY=canvasH-180.0*dpiScale; if (sizeY<60.0*dpiScale) sizeY=60.0*dpiScale; } if (ImGui::BeginTable("PendingSamplesList",1,ImGuiTableFlags_ScrollY,ImVec2(0.0f,sizeY))) { if (sampleBankSearchQuery.empty()) { for (size_t i=0; iname); if (pendingInsSingle) { if (ImGui::Selectable(id.c_str())) { pendingSamples[i].second=true; quitPlease=true; } } else { // TODO:fixstyle from hereonwards ImGuiIO& io = ImGui::GetIO(); if(ImGui::Checkbox(id.c_str(),&pendingSamples[i].second) && io.KeyShift) { for(int jj = (int)i - 1; jj >= 0; jj--) { if(pendingSamples[jj].second) //pressed shift and there's selected item above { for(int k = jj; k < (int)i; k++) { pendingSamples[k].second = true; } break; } } } } if (pendingSamples[i].second) anySelected=true; } } else //display search results { if(reissueSearch) { String lowerCase=sampleBankSearchQuery; for (char& ii: lowerCase) { if (ii>='A' && ii<='Z') ii+='a'-'A'; } sampleBankSearchResults.clear(); for (int j=0; j < (int)pendingSamples.size(); j++) { String lowerCase1 = pendingSamples[j].first->name; for (char& ii: lowerCase1) { if (ii>='A' && ii<='Z') ii+='a'-'A'; } if (lowerCase1.find(lowerCase)!=String::npos) { sampleBankSearchResults.push_back(std::make_pair(pendingSamples[j].first, pendingSamples[j].second)); } } } for (size_t i=0; iname); ImGuiIO& io = ImGui::GetIO(); if(ImGui::Checkbox(id.c_str(),&sampleBankSearchResults[i].second) && io.KeyShift) { for(int jj = (int)i - 1; jj >= 0; jj--) { if(sampleBankSearchResults[jj].second) //pressed shift and there's selected item above { for(int k = jj; k < (int)i; k++) { sampleBankSearchResults[k].second = true; } break; } } } if (sampleBankSearchResults[i].second) anySelected=true; } for (size_t i=0; i 0) { for (size_t j=0; j& i: pendingSamples) { i.second=false; } quitPlease=true; } if (quitPlease) { ImGui::CloseCurrentPopup(); int counter = 0; for (std::pair& i: pendingSamples) { if (!i.second) { delete i.first; } else { if(counter == 0 && replacePendingSample) { *e->song.sample[curSample]=*i.first; replacePendingSample = false; } else { e->addSamplePtr(i.first); } } counter++; } curSample = (int)e->song.sample.size() - 1; pendingSamples.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; i384000) pendingRawSampleRate=384000; } if (pendingRawSampleDepth==DIV_SAMPLE_DEPTH_8BIT || pendingRawSampleDepth==DIV_SAMPLE_DEPTH_16BIT) { ImGui::AlignTextToFramePadding(); ImGui::Text(_("Channels")); ImGui::SameLine(); ImGui::SetNextItemWidth(120.0f*dpiScale); if (ImGui::InputInt("##RSChans",&pendingRawSampleChannels,1,2)) { if (pendingRawSampleChannels<1) pendingRawSampleChannels=1; } ImGui::Text(_("(will be mixed down to mono)")); ImGui::Checkbox(_("Unsigned"),&pendingRawSampleUnsigned); } if (pendingRawSampleDepth==DIV_SAMPLE_DEPTH_16BIT) { ImGui::Checkbox(_("Big endian"),&pendingRawSampleBigEndian); } if (pendingRawSampleDepth==DIV_SAMPLE_DEPTH_YMZ_ADPCM || pendingRawSampleDepth==DIV_SAMPLE_DEPTH_QSOUND_ADPCM || pendingRawSampleDepth==DIV_SAMPLE_DEPTH_ADPCM_A || pendingRawSampleDepth==DIV_SAMPLE_DEPTH_ADPCM_B || pendingRawSampleDepth==DIV_SAMPLE_DEPTH_VOX || pendingRawSampleDepth==DIV_SAMPLE_DEPTH_4BIT) { ImGui::Checkbox(_("Swap nibbles"),&pendingRawSampleSwapNibbles); } if (pendingRawSampleDepth==DIV_SAMPLE_DEPTH_8BIT) { ImGui::Checkbox(_("Swap words"),&pendingRawSampleBigEndian); } if (pendingRawSampleDepth==DIV_SAMPLE_DEPTH_MULAW) { ImGui::Text(_("Encoding:")); ImGui::Indent(); if (ImGui::RadioButton("G.711",pendingRawSampleSwapNibbles==0)) { pendingRawSampleSwapNibbles=0; } if (ImGui::RadioButton("Namco",pendingRawSampleSwapNibbles==1)) { pendingRawSampleSwapNibbles=1; } ImGui::Unindent(); } if (pendingRawSampleDepth==DIV_SAMPLE_DEPTH_1BIT || pendingRawSampleDepth==DIV_SAMPLE_DEPTH_1BIT_DPCM) { ImGui::Checkbox(_("Reverse bit order"),&pendingRawSampleSwapNibbles); } if (ImGui::Button(_("OK"))) { DivSample* s=e->sampleFromFileRaw(pendingRawSample.c_str(),(DivSampleDepth)pendingRawSampleDepth,pendingRawSampleChannels,pendingRawSampleBigEndian,pendingRawSampleUnsigned,pendingRawSampleSwapNibbles,pendingRawSampleRate); 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; }); updateSampleTex=true; } 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); #ifdef NO_INTRO introPos=12.0; #else if ((!tutorial.introPlayed || settings.alwaysPlayIntro!=0) && renderBackend!=GUI_BACKEND_SOFTWARE) { MEASURE_BEGIN(intro); initialScreenWipe=0; if (settings.alwaysPlayIntro==1) { shortIntro=true; } drawIntro(introPos); MEASURE_END(intro); } else { introPos=12.0; } #endif #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 if (settings.displayRenderTime) { String renderTime=fmt::sprintf("%.0fµs",ImGui::GetIO().DeltaTime*1000000.0); String renderTime2=fmt::sprintf("%.1f FPS",1.0/ImGui::GetIO().DeltaTime); ImDrawList* dl=ImGui::GetForegroundDrawList(); ImVec2 markPos=ImVec2(canvasW-ImGui::CalcTextSize(renderTime.c_str()).x-60.0*dpiScale,4.0*dpiScale); ImVec2 markPos2=ImVec2(canvasW-ImGui::CalcTextSize(renderTime2.c_str()).x-160.0*dpiScale,4.0*dpiScale); dl->AddText(markPos,0xffffffff,renderTime.c_str()); dl->AddText(markPos2,0xffffffff,renderTime2.c_str()); //logV("%s (%s)",renderTime,renderTime2); } layoutTimeEnd=SDL_GetPerformanceCounter(); // backup trigger if (modified && settings.backupEnable) { 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=settings.backupInterval; backupLock.unlock(); return true; } if (!dirExists(backupPath.c_str())) { if (!makeDir(backupPath.c_str())) { logW("could not create backup directory!"); backupTimer=settings.backupInterval; 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=settings.backupInterval; 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(); failedNoteOn=false; break; case 1: e->stopWavePreview(); break; case 2: e->stopSamplePreview(); break; } } } if (!settings.renderClearPos || renderBackend==GUI_BACKEND_METAL) { 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--; if (mustClear==0) e->everythingOK(); } 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)); } } else if (settings.disableFadeIn) { initialScreenWipe=0.0f; } } drawTimeEnd=SDL_GetPerformanceCounter(); swapTimeBegin=SDL_GetPerformanceCounter(); if (!settings.vsync || !rend->canVSync()) { if (settings.frameRateLimit>0) { unsigned int presentDelay=SDL_GetPerformanceFrequency()/settings.frameRateLimit; if ((nextPresentTime-swapTimeBegin)present(); if (settings.renderClearPos && renderBackend!=GUI_BACKEND_METAL) { rend->clear(uiColors[GUI_COLOR_BACKGROUND]); } swapTimeEnd=SDL_GetPerformanceCounter(); layoutTimeDelta=layoutTimeEnd-layoutTimeBegin; renderTimeDelta=renderTimeEnd-renderTimeBegin; drawTimeDelta=drawTimeEnd-drawTimeBegin; swapTimeDelta=swapTimeEnd-swapTimeBegin; 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; wantScrollListIns=false; wantScrollListWave=false; wantScrollListSample=false; pressedPoints.clear(); releasedPoints.clear(); if (willCommit) { commitSettings(); willCommit=false; } // To check for instrument editor modification, we need an up-to-date `insEditMayBeDirty` // (based on incoming user input events), and we want any possible instrument modifications // to already have been made. checkRecordInstrumentUndoStep(); // the following code handles order lock (if it is enabled). if (orderLock) { cursor.order=curOrder; selStart.order=curOrder; selEnd.order=curOrder; } 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 (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 (!editOptsVisible) { latchTarget=0; latchNibble=false; } if (SDL_GetWindowFlags(sdlWin)&SDL_WINDOW_MINIMIZED) { SDL_Delay(100); } } return false; } bool FurnaceGUI::init() { logI("initializing GUI."); opTouched=new bool[DIV_MAX_PATTERNS*DIV_MAX_ROWS]; syncState(); syncSettings(); syncTutorial(); recentFile.clear(); for (int i=0; igetConfString(fmt::sprintf("recentFile%d",i),""); if (!r.empty()) { recentFile.push_back(r); } } if (!settings.persistFadeOut) { audioExportOptions.loops=settings.exportLoops; audioExportOptions.fadeOut=settings.exportFadeOut; } initSystemPresets(); 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 // This sets the icon in wayland SDL_setenv("SDL_VIDEO_WAYLAND_WMCLASS", FURNACE_APP_ID, 0); // 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",GUI_WIDTH_DEFAULT); scrH=scrConfH=e->getConfInt("lastWindowHeight",GUI_HEIGHT_DEFAULT); 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","Software"); e->saveConf(); lastError=fmt::sprintf(_("could not init renderer!\nfalling back to software renderer. please restart Furnace.")); } else if (settings.renderBackend=="SDL") { lastError=fmt::sprintf(_("could not init renderer! %s\nfalling back to software renderer. please restart Furnace."),SDL_GetError()); settings.renderBackend="Software"; e->setConf("renderBackend","Software"); e->saveConf(); } else { lastError=fmt::sprintf(_("could not init renderer!")); } return false; } rend->preInit(e->getConfObject()); 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) { const char* sdlErr=SDL_GetError(); lastError=fmt::sprintf(_("could not open window! %s"),sdlErr); if (settings.renderBackend!="Software" && strstr(sdlErr,"matching")!=NULL) { settings.renderBackend="Software"; e->setConf("renderBackend","Software"); e->saveConf(); lastError+=_("\nfalling back to software renderer. please restart Furnace."); } 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 if (!settings.noMaximizeWorkaround) { 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,settings.vsync)) { logE("it failed..."); if (settings.renderBackend!="Software") { settings.renderBackend="Software"; e->setConf("renderBackend","Software"); e->saveConf(); lastError=fmt::sprintf(_("could not init renderer!\nfalling back to software renderer. please restart Furnace.")); } else if (settings.renderBackend=="SDL") { lastError=fmt::sprintf(_("could not init renderer! %s\nfalling back to software renderer. please restart Furnace."),SDL_GetError()); settings.renderBackend="Software"; e->setConf("renderBackend","Software"); e->saveConf(); } else { lastError=fmt::sprintf(_("could not init renderer!")); } return false; } logV("render backend started"); // set best texture format unsigned int availTexFormats=rend->getTextureFormats(); if (availTexFormats&GUI_TEXFORMAT_ABGR32) { bestTexFormat=GUI_TEXFORMAT_ABGR32; } else if (availTexFormats&GUI_TEXFORMAT_ARGB32) { bestTexFormat=GUI_TEXFORMAT_ARGB32; } else if (availTexFormats&GUI_TEXFORMAT_RGBA32) { bestTexFormat=GUI_TEXFORMAT_RGBA32; } else if (availTexFormats&GUI_TEXFORMAT_BGRA32) { bestTexFormat=GUI_TEXFORMAT_BGRA32; } // 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(); updateROMExportAvail(); logV("max texture size: %dx%d",rend->getMaxTextureWidth(),rend->getMaxTextureHeight()); rend->clear(ImVec4(0.0,0.0,0.0,1.0)); rend->present(); logD("preparing user interface..."); IMGUI_CHECKVERSION(); ImGui::CreateContext(); rend->initGUI(sdlWin); ImGuiLocEntry guiLocalization[12]; guiLocalization[0].Key=ImGuiLocKey_TableSizeOne; guiLocalization[0].Text=_("Size column to fit###SizeOne"); guiLocalization[1].Key=ImGuiLocKey_TableSizeAllFit; guiLocalization[1].Text=_("Size all columns to fit###SizeAll"); guiLocalization[2].Key=ImGuiLocKey_TableSizeAllDefault; guiLocalization[2].Text=_("Size all columns to default###SizeAll"); guiLocalization[3].Key=ImGuiLocKey_TableResetOrder; guiLocalization[3].Text=_("Reset order###ResetOrder"); guiLocalization[4].Key=ImGuiLocKey_WindowingMainMenuBar; guiLocalization[4].Text=_("(Main menu bar)"); guiLocalization[5].Key=ImGuiLocKey_WindowingPopup; guiLocalization[5].Text=_("(Popup)"); guiLocalization[6].Key=ImGuiLocKey_WindowingUntitled; guiLocalization[6].Text=_("(Untitled)"); guiLocalization[7].Key=ImGuiLocKey_OpenLink_s; guiLocalization[7].Text=_("Open '%s'"); guiLocalization[8].Key=ImGuiLocKey_CopyLink; guiLocalization[8].Text=_("Copy Link###CopyLink"); guiLocalization[9].Key=ImGuiLocKey_DockingHideTabBar; guiLocalization[9].Text=_("Hide tab bar###HideTabBar"); guiLocalization[10].Key=ImGuiLocKey_DockingHoldShiftToDock; guiLocalization[10].Text=_("Hold SHIFT to enable Docking window."); guiLocalization[11].Key=ImGuiLocKey_DockingDragToUndockOrMoveNode, guiLocalization[11].Text=_("Click and drag to move or undock whole node."); ImGui::LocalizeRegisterEntries(guiLocalization,12); const char* localeSettings=_("LocaleSettings: ccjk"); if (strlen(localeSettings)<20) { logE("the LocaleSettings string is incomplete!"); } else { localeRequiresChinese=(localeSettings[16]=='C'); localeRequiresChineseTrad=(localeSettings[17]=='C'); localeRequiresJapanese=(localeSettings[18]=='J'); localeRequiresKorean=(localeSettings[19]=='K'); if (strlen(localeSettings)>21) { if (localeSettings[20]==' ') { ImWchar next=0; for (const char* i=&localeSettings[21]; *i; i++) { if (((*i)>='0' && (*i)<='9') || ((*i)>='A' && (*i)<='F')) { next<<=4; if ((*i)>='0' && (*i)<='9') { next|=(*i)-'0'; } else { next|=(*i)-'A'+10; } } else { localeExtraRanges.push_back(next); next=0; } } if (next!=0) { localeExtraRanges.push_back(next); } localeExtraRanges.push_back(0); } } } if (!localeExtraRanges.empty()) { logV("locale extra ranges:"); for (ImWchar i: localeExtraRanges) { logV("%x",i); } } loadUserPresets(true); applyUISettings(); logD("building font..."); if (rend->areTexturesSquare()) { ImGui::GetIO().Fonts->Flags|=ImFontAtlasFlags_Square; } 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); firstFrame=true; userEvents=SDL_RegisterEvents(1); e->setMidiCallback([this](const TAMidiMessage& msg) -> int { if (introPos<11.0) return -3; midiLock.lock(); midiQueue.push(msg); if (userEvents!=0xffffffff && midiWakeUp) { midiWakeUp=false; userEvent.user.type=userEvents; userEvent.user.code=0; userEvent.user.data1=NULL; userEvent.user.data2=NULL; SDL_PushEvent(&userEvent); } midiLock.unlock(); e->setMidiBaseChan(cursor.xCoarse); if (msg.type==TA_MIDI_SYSEX) return -3; if (midiMap.valueInputStyle!=0 && cursor.xFine!=0 && edit) return -3; if (!midiMap.noteInput) return -3; if (learning!=-1) return -3; if (midiMap.at(msg)) return -3; 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 -3; } 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 -3; } if (midiMap.directChannel && midiMap.directProgram) return -1; 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; time_t thisMakesNoSense=time(NULL); struct tm curTime; #ifdef _WIN32 struct tm* tempTM=localtime(&thisMakesNoSense); if (tempTM==NULL) { memset(&curTime,0,sizeof(struct tm)); } else { memcpy(&curTime,tempTM,sizeof(struct tm)); purgeYear=1900+curTime.tm_year-1; purgeMonth=curTime.tm_mon+1; purgeDay=curTime.tm_mday; } #else if (localtime_r(&thisMakesNoSense,&curTime)==NULL) { memset(&curTime,0,sizeof(struct tm)); } else { purgeYear=1900+curTime.tm_year-1; purgeMonth=curTime.tm_mon+1; purgeDay=curTime.tm_mday; } #endif // initialize audio formats String compatFormats; audioLoadFormats.push_back(_("compatible files")); audioLoadFormats.push_back(""); #ifdef HAVE_SNDFILE int value=0; sf_command(NULL,SFC_GET_FORMAT_MAJOR_COUNT,&value,sizeof(int)); logV("simple formats: %d",value); for (int i=0; igetConfString("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); 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); workingDirConfig=e->getConfString("lastDirConfig",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); xyOscOpen=e->getConfBool("xyOscOpen",false); memoryOpen=e->getConfBool("memoryOpen",false); csPlayerOpen=e->getConfBool("csPlayerOpen",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); userPresetsOpen=e->getConfBool("userPresetsOpen",false); 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); int extraChannelButtons=e->getConfInt("extraChannelButtons",0); if (!e->hasConf("patExtraButtons")) { patExtraButtons=(extraChannelButtons==1); } else { patExtraButtons=e->getConfBool("patExtraButtons",false); } if (!e->hasConf("patChannelNames")) { patChannelNames=(extraChannelButtons==2); } else { patChannelNames=e->getConfBool("patChannelNames",false); } patChannelPairs=e->getConfBool("patChannelPairs",true); patChannelHints=e->getConfInt("patChannelHints",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); orderLock=e->getConfBool("orderLock",false); followOrders=e->getConfBool("followOrders",true); followPattern=e->getConfBool("followPattern",true); noteInputPoly=e->getConfBool("noteInputPoly",true); audioExportOptions.loops=e->getConfInt("exportLoops",0); if (audioExportOptions.loops<0) audioExportOptions.loops=0; audioExportOptions.fadeOut=e->getConfDouble("exportFadeOut",0.0); if (audioExportOptions.fadeOut<0.0) audioExportOptions.fadeOut=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); chanOscCenterStrat=e->getConfInt("chanOscCenterStrat",1); chanOscTextX=e->getConfFloat("chanOscTextX",0.0f); chanOscTextY=e->getConfFloat("chanOscTextY",0.0f); chanOscAmplify=e->getConfFloat("chanOscAmplify",0.95f); chanOscLineSize=e->getConfFloat("chanOscLineSize",1.0f); chanOscWindowSize=e->getConfFloat("chanOscWindowSize",20.0f); chanOscWaveCorr=e->getConfBool("chanOscWaveCorr",true); chanOscOptions=e->getConfBool("chanOscOptions",false); chanOscNormalize=e->getConfBool("chanOscNormalize",false); chanOscRandomPhase=e->getConfBool("chanOscRandomPhase",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(); xyOscXChannel=e->getConfInt("xyOscXChannel",0); xyOscXInvert=e->getConfBool("xyOscXInvert",false); xyOscYChannel=e->getConfInt("xyOscYChannel",1); xyOscYInvert=e->getConfBool("xyOscYInvert",false); xyOscZoom=e->getConfFloat("xyOscZoom",1.0f); xyOscSamples=e->getConfInt("xyOscSamples",32768); xyOscDecayTime=e->getConfFloat("xyOscDecayTime",10.0f); xyOscIntensity=e->getConfFloat("xyOscIntensity",2.0f); xyOscThickness=e->getConfFloat("xyOscThickness",2.0f); cvHiScore=e->getConfInt("cvHiScore",25000); } void FurnaceGUI::commitState(DivConfig& conf) { if (!mobileUI) { if (!ImGui::SaveIniSettingsToDisk(finalLayoutPath,true)) { reportError(fmt::sprintf(_("could NOT save layout! %s"),strerror(errno))); } } conf.set("configVersion",(int)DIV_ENGINE_VERSION); conf.set("lastDir",workingDir); conf.set("lastDirSong",workingDirSong); conf.set("lastDirIns",workingDirIns); conf.set("lastDirWave",workingDirWave); conf.set("lastDirSample",workingDirSample); conf.set("lastDirAudioExport",workingDirAudioExport); conf.set("lastDirVGMExport",workingDirVGMExport); conf.set("lastDirROMExport",workingDirROMExport); conf.set("lastDirFont",workingDirFont); conf.set("lastDirColors",workingDirColors); conf.set("lastDirKeybinds",workingDirKeybinds); conf.set("lastDirLayout",workingDirLayout); conf.set("lastDirConfig",workingDirConfig); conf.set("lastDirTest",workingDirTest); // commit last open windows conf.set("editControlsOpen",editControlsOpen); conf.set("ordersOpen",ordersOpen); conf.set("insListOpen",insListOpen); conf.set("songInfoOpen",songInfoOpen); conf.set("patternOpen",patternOpen); conf.set("insEditOpen",insEditOpen); conf.set("waveListOpen",waveListOpen); conf.set("waveEditOpen",waveEditOpen); conf.set("sampleListOpen",sampleListOpen); conf.set("sampleEditOpen",sampleEditOpen); conf.set("settingsOpen",settingsOpen); conf.set("mixerOpen",mixerOpen); conf.set("oscOpen",oscOpen); conf.set("chanOscOpen",chanOscOpen); conf.set("xyOscOpen",xyOscOpen); conf.set("memoryOpen",memoryOpen); conf.set("csPlayerOpen",csPlayerOpen); conf.set("volMeterOpen",volMeterOpen); conf.set("statsOpen",statsOpen); conf.set("compatFlagsOpen",compatFlagsOpen); conf.set("pianoOpen",pianoOpen); conf.set("notesOpen",notesOpen); conf.set("channelsOpen",channelsOpen); conf.set("patManagerOpen",patManagerOpen); conf.set("sysManagerOpen",sysManagerOpen); conf.set("clockOpen",clockOpen); conf.set("speedOpen",speedOpen); conf.set("groovesOpen",groovesOpen); conf.set("regViewOpen",regViewOpen); conf.set("logOpen",logOpen); conf.set("effectListOpen",effectListOpen); conf.set("subSongsOpen",subSongsOpen); conf.set("findOpen",findOpen); conf.set("spoilerOpen",spoilerOpen); conf.set("userPresetsOpen",userPresetsOpen); // commit dir state conf.set("insListDir",insListDir); conf.set("waveListDir",waveListDir); conf.set("sampleListDir",sampleListDir); // commit last window size conf.set("lastWindowWidth",scrConfW); conf.set("lastWindowHeight",scrConfH); conf.set("lastWindowX",settings.saveWindowPos?scrConfX:(int)SDL_WINDOWPOS_CENTERED); conf.set("lastWindowY",settings.saveWindowPos?scrConfY:(int)SDL_WINDOWPOS_CENTERED); conf.set("lastWindowMax",scrMax); conf.set("tempoView",tempoView); conf.set("waveHex",waveHex); conf.set("waveSigned",waveSigned); conf.set("waveGenVisible",waveGenVisible); conf.set("waveEditStyle",waveEditStyle); conf.set("patExtraButtons",patExtraButtons); conf.set("patChannelNames",patChannelNames); conf.set("patChannelPairs",patChannelPairs); conf.set("patChannelHints",(int)patChannelHints); conf.set("lockLayout",lockLayout); conf.set("fullScreen",fullScreen); conf.set("mobileUI",mobileUI); conf.set("edit",edit); conf.set("orderLock",orderLock); conf.set("followOrders",followOrders); conf.set("followPattern",followPattern); conf.set("orderEditMode",orderEditMode); conf.set("noteInputPoly",noteInputPoly); if (settings.persistFadeOut) { conf.set("exportLoops",audioExportOptions.loops); conf.set("exportFadeOut",audioExportOptions.fadeOut); } // commit oscilloscope state conf.set("oscZoom",oscZoom); conf.set("oscZoomSlider",oscZoomSlider); conf.set("oscWindowSize",oscWindowSize); // commit piano state conf.set("pianoOctaves",pianoOctaves); conf.set("pianoOctavesEdit",pianoOctavesEdit); conf.set("pianoOptions",pianoOptions); conf.set("pianoSharePosition",pianoSharePosition); conf.set("pianoOptionsSet",pianoOptionsSet); conf.set("pianoReadonly",pianoReadonly); conf.set("pianoOffset",pianoOffset); conf.set("pianoOffsetEdit",pianoOffsetEdit); conf.set("pianoView",pianoView); conf.set("pianoInputPadMode",pianoInputPadMode); // commit per-chan osc state conf.set("chanOscCols",chanOscCols); conf.set("chanOscAutoColsType",chanOscAutoColsType); conf.set("chanOscColorX",chanOscColorX); conf.set("chanOscColorY",chanOscColorY); conf.set("chanOscCenterStrat",chanOscCenterStrat); conf.set("chanOscTextX",chanOscTextX); conf.set("chanOscTextY",chanOscTextY); conf.set("chanOscAmplify",chanOscAmplify); conf.set("chanOscLineSize",chanOscLineSize); conf.set("chanOscWindowSize",chanOscWindowSize); conf.set("chanOscWaveCorr",chanOscWaveCorr); conf.set("chanOscOptions",chanOscOptions); conf.set("chanOscNormalize",chanOscNormalize); conf.set("chanOscRandomPhase",chanOscRandomPhase); conf.set("chanOscTextFormat",chanOscTextFormat); conf.set("chanOscColorR",chanOscColor.x); conf.set("chanOscColorG",chanOscColor.y); conf.set("chanOscColorB",chanOscColor.z); conf.set("chanOscColorA",chanOscColor.w); conf.set("chanOscTextColorR",chanOscTextColor.x); conf.set("chanOscTextColorG",chanOscTextColor.y); conf.set("chanOscTextColorB",chanOscTextColor.z); conf.set("chanOscTextColorA",chanOscTextColor.w); conf.set("chanOscUseGrad",chanOscUseGrad); conf.set("chanOscGrad",chanOscGrad.toString()); // commit x-y osc state conf.set("xyOscXChannel",xyOscXChannel); conf.set("xyOscXInvert",xyOscXInvert); conf.set("xyOscYChannel",xyOscYChannel); conf.set("xyOscYInvert",xyOscYInvert); conf.set("xyOscZoom",xyOscZoom); conf.set("xyOscSamples",xyOscSamples); conf.set("xyOscDecayTime",xyOscDecayTime); conf.set("xyOscIntensity",xyOscIntensity); conf.set("xyOscThickness",xyOscThickness); // 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()) { conf.set(key,""); } else { conf.set(key,recentFile[i]); } } conf.set("cvHiScore",cvHiScore); } bool FurnaceGUI::finish(bool saveConfig) { if (!quitNoSave) { commitState(e->getConfObject()); if (userPresetsOpen) { saveUserPresets(true); } if (saveConfig) { logI("saving config."); e->saveConf(); } } rend->quitGUI(); ImGui_ImplSDL2_Shutdown(); quitRender(); ImGui::DestroyContext(); SDL_DestroyWindow(sdlWin); if (vibrator) { SDL_HapticClose(vibrator); } for (int i=0; i