#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 "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) { if (note==DIV_NOTE_OFF) { return noteOffLabel; } else if (note==DIV_NOTE_REL) { // note off and envelope release return noteRelLabel; } else if (note==DIV_MACRO_REL) { // envelope release only return macroRelLabel; } else if (note==-1) { return emptyLabel; } else if (note==DIV_NOTE_NULL_PAT) { return "BUG"; } if (note<0 || note>=180) { return "???"; } if (settings.flatNotes) { if (settings.germanNotation) return noteNamesGF[note]; return noteNamesF[note]; } if (settings.germanNotation) return noteNamesG[note]; return noteNames[note]; } bool FurnaceGUI::decodeNote(const char* what, short& note) { if (strlen(what)!=3) return false; if (strcmp(what,"...")==0) { note=-1; return true; } if (strcmp(what,"???")==0) { note=DIV_NOTE_NULL_PAT; return true; } if (strcmp(what,"OFF")==0) { note=DIV_NOTE_OFF; return true; } if (strcmp(what,"===")==0) { note=DIV_NOTE_REL; return true; } if (strcmp(what,"REL")==0) { note=DIV_MACRO_REL; return true; } for (int i=0; i<180; i++) { if (strcmp(what,noteNames[i])==0) { note=i; 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; } } void FurnaceGUI::VerticalText(const char* fmt, ...) { va_list args; va_start(args, fmt); ImVec2 pos=ImGui::GetCursorScreenPos(); ImDrawList* dl=ImGui::GetWindowDrawList(); int vtxBegin, vtxEnd; vtxBegin=dl->_VtxCurrentIdx; char text[4096]; vsnprintf(text, 4096, fmt, args); ImVec2 size=ImGui::CalcTextSize(text); dl->AddText(pos, ImGui::GetColorU32(ImGuiCol_Text), text); vtxEnd=dl->_VtxCurrentIdx; ImGui::ShadeVertsTransformPos(dl, vtxBegin, vtxEnd, pos+ImVec2(size.x,0), 0, -1, ImGui::GetCursorScreenPos()); ImGui::Dummy(ImVec2(size.y,size.x)); } void FurnaceGUI::VerticalText(float maxSize, bool centered, const char* fmt, ...) { va_list args; va_start(args, fmt); ImVec2 pos=ImGui::GetWindowPos(); ImDrawList* dl=ImGui::GetWindowDrawList(); int vtxBegin, vtxEnd; vtxBegin=dl->_VtxCurrentIdx; char text[4096]; vsnprintf(text, 4096, fmt, args); const char* textEol=ImGui::FindRenderedTextEnd(text); ImVec2 size=ImGui::CalcTextSize(text); dl->PushClipRect(pos,pos+ImGui::GetWindowSize()); ImGui::RenderTextEllipsis(dl,pos,pos+ImVec2(maxSize,ImGui::GetFontSize()),maxSize,text,textEol,&size); dl->PopClipRect(); vtxEnd=dl->_VtxCurrentIdx; float ySize=(size.x>maxSize)?maxSize:size.x; ImGui::ShadeVertsTransformPos(dl, vtxBegin, vtxEnd, pos, 0, -1, ImGui::GetCursorScreenPos()+ImVec2(0,(size.x+ySize)/2+(centered?(maxSize-size.x)/2.0f:0))); ImGui::Dummy(ImVec2(size.y,centered?maxSize:ySize)); } 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::NoteSelector(int* value, bool showOffRel, int octaveMin, int octaveMax) { bool ret=false, calcNote=false; char tempID[64]; if (*value==DIV_MACRO_REL) { snprintf(tempID,64,"%s##MREL",macroRelLabel); } else if (*value==DIV_NOTE_REL) { snprintf(tempID,64,"%s##NREL",noteRelLabel); } else if (*value==DIV_NOTE_OFF) { snprintf(tempID,64,"%s##NOFF",noteOffLabel); } else if (*value>=0 && *value<180) { snprintf(tempID,64,"%c%c",noteNames[60+(*value%12)][0],(noteNames[60+(*value%12)][1]=='-')?' ':noteNames[60+(*value%12)][1]); } else { snprintf(tempID,64,"???"); *value=0; } float width=ImGui::GetContentRegionAvail().x/2-ImGui::GetStyle().FramePadding.x; ImGui::SetNextItemWidth(width); int note=(*value)%12; int oct=-5; if (*value<180) oct=(*value-note-60)/12; ImGui::BeginGroup(); ImGui::PushID(value); if (ImGui::BeginCombo("##NoteSelectorNote",tempID)) { for (int j=0; j<12; j++) { snprintf(tempID,64,"%c%c",noteNames[60+j][0],(noteNames[60+j][1]=='-')?' ':noteNames[60+j][1]); if (ImGui::Selectable(tempID,note==j && *value<180)) { note=j; calcNote=true; } if (note==j && *value<180) ImGui::SetItemDefaultFocus(); } if (showOffRel) { if (ImGui::Selectable(noteOffLabel,*value==DIV_NOTE_OFF)) { *value=DIV_NOTE_OFF; ret=true; } if (ImGui::Selectable(noteRelLabel,*value==DIV_NOTE_REL)) { *value=DIV_NOTE_REL; ret=true; } if (ImGui::Selectable(macroRelLabel,*value==DIV_MACRO_REL)) { *value=DIV_MACRO_REL; ret=true; } if (*value>=DIV_NOTE_OFF && *value<=DIV_MACRO_REL) ImGui::SetItemDefaultFocus(); } ImGui::EndCombo(); } ImGui::SameLine(); if (*value<180) { ImGui::SetNextItemWidth(width/2); if (ImGui::InputScalar("##NoteSelectorOctave",ImGuiDataType_S32,&oct)) { if (octoctaveMax) oct=octaveMax; calcNote=true; } } if (calcNote) { *value=(oct+5)*12+note; ret=true; } ImGui::PopID(); ImGui::EndGroup(); return ret; } 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::setCurIns(int newIns) { curIns=newIns; memset(multiIns,-1,7*sizeof(int)); } bool FurnaceGUI::setMultiIns(int newIns) { // set primary instrument if not set (or if polyphony is mono) if (curIns<0 || noteInputMode==GUI_NOTE_INPUT_MONO) { setCurIns(newIns); return true; } // don't allow using the primary instrument twice if (curIns==newIns) return false; for (int i=0; i<7; i++) { // don't allow using an instrument more than once if (multiIns[i]==newIns) return false; // if slot is empty, assign instrument to it if (multiIns[i]==-1) { multiIns[i]=newIns; return true; } } // no more slots available return false; } bool FurnaceGUI::isMultiInsActive() { for (int i=0; i<7; i++) { if (multiIns[i]>=0) return true; } return 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 e->curSubSong->chanColor[ch]?ImGui::ColorConvertU32ToFloat4(e->curSubSong->chanColor[ch]):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][Tuner]\n\ Pos=60,60\n\ Size=160,170\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\ [Window][Multi-Ins Setup]\n\ Pos=858,549\n\ Size=250,165\n\ Collapsed=0\n\ \n\ [Window][Spectrum]\n\ Pos=771,384\n\ Size=600,244\n\ Collapsed=0\n\ \n\ [Window][Music Player]\n\ Pos=704,243\n\ Size=413,115\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 speedSum=0; for (int i=0; igetStreamPlayer()) { e->killStream(); } memset(chanOscVol,0,DIV_MAX_CHANS*sizeof(float)); for (int i=0; isetOrder(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; chordInputOffset=0; 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->stop(); curNibble=false; orderNibble=false; chordInputOffset=0; 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; for (int mi=0; mi<7; mi++) { if (multiIns[mi]!=-1) { e->autoNoteOn(-1,multiIns[mi],note,-1,multiInsTranspose[mi]); } } }); } 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 chanOff) { int ch=cursor.xCoarse; int ord=curOrder; int y=cursor.y; int tick=0; int speed=0; if (chanOff>0 && noteInputMode==GUI_NOTE_INPUT_CHORD) { ch=e->getViableChannel(ch,chanOff,curIns); if ((!e->isPlaying() || !followPattern)) { y-=editStep; while (y<0) { if (--ord<0) ord=0; y+=e->curSubSong->patLen; } } } 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("noteInput: chan %d, offset %d, %d:%d %d/%d",ch,chanOff,ord,y,tick,speed); DivPattern* pat=e->curPat[ch].getPattern(e->curOrders->ord[ch][ord],true); bool removeIns=false; prepareUndo(GUI_UNDO_PATTERN_EDIT,UndoRegion(ord,ch,y,ord,ch,y)); if (key==GUI_NOTE_OFF) { // note off pat->newData[y][DIV_PAT_NOTE]=DIV_NOTE_OFF; removeIns=true; } else if (key==GUI_NOTE_OFF_RELEASE) { // note off + env release pat->newData[y][DIV_PAT_NOTE]=DIV_NOTE_REL; removeIns=true; } else if (key==GUI_NOTE_RELEASE) { // env release only pat->newData[y][DIV_PAT_NOTE]=DIV_MACRO_REL; removeIns=true; } else { pat->newData[y][DIV_PAT_NOTE]=num+60; if (latchIns==-2) { if (curIns>=(int)e->song.ins.size()) curIns=-1; if (curIns>=0) { pat->newData[y][DIV_PAT_INS]=curIns; } } else if (latchIns!=-1 && !e->song.ins.empty()) { pat->newData[y][DIV_PAT_INS]=MIN(((int)e->song.ins.size())-1,latchIns); } int maxVol=e->getMaxVolumeChan(ch); if (latchVol!=-1) { pat->newData[y][DIV_PAT_VOL]=MIN(maxVol,latchVol); } else if (vol!=-1) { pat->newData[y][DIV_PAT_VOL]=e->mapVelocity(ch,pow((float)vol/127.0f,midiMap.volExp)); } if (latchEffect!=-1) pat->newData[y][DIV_PAT_FX(0)]=latchEffect; if (latchEffectVal!=-1) pat->newData[y][DIV_PAT_FXVAL(0)]=latchEffectVal; } if (removeIns) { if (settings.removeInsOff) { pat->newData[y][DIV_PAT_INS]=-1; } if (settings.removeVolOff) { pat->newData[y][DIV_PAT_VOL]=-1; } } if ((!e->isPlaying() || !followPattern) && (chanOff<1 || noteInputMode!=GUI_NOTE_INPUT_CHORD)) { editAdvance(); } makeUndo(GUI_UNDO_PATTERN_EDIT,UndoRegion(ord,ch,y,ord,ch,y)); curNibble=false; } void FurnaceGUI::valueInput(int num, bool direct, int target) { int ch=cursor.xCoarse; int ord=curOrder; int y=cursor.y; logV("valueInput: chan %d, %d:%d",ch,ord,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; if (direct) { pat->newData[y][target]=num&0xff; } else { if (pat->newData[y][target]==-1) pat->newData[y][target]=0; if (!settings.pushNibble && !curNibble) { pat->newData[y][target]=num; } else { pat->newData[y][target]=((pat->newData[y][target]<<4)|num)&0xff; } } // TODO: shouldn't this be target? if (cursor.xFine==DIV_PAT_INS) { // instrument if (pat->newData[y][target]>=(int)e->song.ins.size()) { pat->newData[y][target]&=0x0f; if (pat->newData[y][target]>=(int)e->song.ins.size()) { pat->newData[y][target]=(int)e->song.ins.size()-1; } } if (settings.absorbInsInput) { setCurIns(pat->newData[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==DIV_PAT_VOL) { if (curNibble) { if (pat->newData[y][target]>e->getMaxVolumeChan(ch)) pat->newData[y][target]=e->getMaxVolumeChan(ch); } else { pat->newData[y][target]&=15; } if (direct) { curNibble=false; } else { if (e->getMaxVolumeChan(ch)<16) { curNibble=false; if (pat->newData[y][target]>e->getMaxVolumeChan(ch)) pat->newData[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); } } } } 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 (!newFilePicker->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,-1,chordInputOffset); } chordInputOffset++; } } 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 (newFilePicker->isOpened()) { if (action!=GUI_ACTION_OCTAVE_UP && action!=GUI_ACTION_OCTAVE_DOWN) return; } doAction(action); return; } } } void FurnaceGUI::keyUp(SDL_Event& ev) { // this is very, very lazy... but it works. chordInputOffset=0; } 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 *.eif *.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", _("Echo instrument"), "*.eif", _("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]; e->notifyInsChange(prevIns); } } else { e->loadTempIns(instruments[0]); if (curIns!=-2) { prevIns=curIns; } setCurIns(-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"), {audioExportFilterName, "*"+audioExportFilterExt}, workingDirAudioExport, dpiScale, (settings.autoFillSave)?shortName:"" ); break; case GUI_FILE_EXPORT_AUDIO_PER_SYS: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); hasOpened=fileDialog->openSave( _("Export Audio"), {audioExportFilterName, "*"+audioExportFilterExt}, workingDirAudioExport, dpiScale, (settings.autoFillSave)?shortName:"" ); break; case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); hasOpened=fileDialog->openSave( _("Export Audio"), {audioExportFilterName, "*"+audioExportFilterExt}, 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_MUSIC_OPEN: if (!dirExists(workingDirMusic)) workingDirMusic=getHomeDir(); hasOpened=fileDialog->openLoad( _("Open Audio File"), audioLoadFormats, workingDirMusic, dpiScale, NULL, false ); 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; } 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); } 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->calcSongTimestamps(); // 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) { e->calcSongTimestamps(); DivSongTimestamps& ts=e->curSubSong->ts; songLength=ts.totalTime.toDouble(); double loopLength=songLength-(ts.loopStartTime.seconds+(double)ts.loopStartTime.micros/1000000.0); e->saveAudio(path.c_str(),audioExportOptions); totalFiles=0; e->getTotalAudioFiles(totalFiles); int totalLoops=0; if (ts.isLoopable) { e->getTotalLoops(totalLoops); songLength+=loopLength*totalLoops; songLength+=audioExportOptions.fadeOut; } totalLength=songLength*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; notifySampleChange=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->newData[cursor.y][DIV_PAT_INS]; latchVol=pat->newData[cursor.y][DIV_PAT_VOL]; latchEffect=pat->newData[cursor.y][DIV_PAT_FX(0)]; latchEffectVal=pat->newData[cursor.y][DIV_PAT_FXVAL(0)]; 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; newFilePicker->setMobile(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; newFilePicker->setMobile(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 (!selectingFull) { 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(tuner) DECLARE_METRIC(spectrum) 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(refPlayer) DECLARE_METRIC(multiInsSetup) 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; SelectionPoint prevCursor=cursor; 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 || (newFilePicker->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 if (!ImGui::GetIO().WantCaptureKeyboard || (newFilePicker->isOpened() && !ImGui::GetIO().WantTextInput)) { keyUp(ev); } 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) { setCurIns(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; notifySampleChange=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_OFF: chordInputOffset=0; break; 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, chordInputOffset ); chordInputOffset++; } } 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)) { setCurIns(msg.data[0]); if (curIns>=(int)e->song.ins.size()) setCurIns(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); } if (notifySampleChange) { notifySampleChange=false; e->notifySampleChange(curSample); } 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 audio resets TAAudioDeviceStatus audioStatus=e->getAudioDeviceStatus(); if (audioStatus!=TA_AUDIO_DEVICE_OK) { logI("audio device reset!"); e->acceptAudioDeviceStatus(); if (!e->switchMaster(false)) { showError(_("audio device has reset or has been disconnected! check audio settings.")); } } // 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(tunerOpen); IMPORT_CLOSE(spectrumOpen); 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); IMPORT_CLOSE(refPlayerOpen); IMPORT_CLOSE(multiInsSetupOpen); } 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; recalcTimestamps=true; } 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; recalcTimestamps=true; 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; recalcTimestamps=true; } 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; if (ImGui::MenuItem(_("tuner"),BIND_FOR(GUI_ACTION_WINDOW_TUNER),tunerOpen)) tunerOpen=!tunerOpen; if (ImGui::MenuItem(_("spectrum"),BIND_FOR(GUI_ACTION_WINDOW_SPECTRUM),spectrumOpen)) spectrumOpen=!spectrumOpen; ImGui::EndMenu(); } if (ImGui::BeginMenu(_("tempo"))) { 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 (ImGui::MenuItem(_("reference music player"),BIND_FOR(GUI_ACTION_WINDOW_REF_PLAYER),refPlayerOpen)) refPlayerOpen=!refPlayerOpen; if (ImGui::MenuItem(_("multi-ins setup"),BIND_FOR(GUI_ACTION_WINDOW_MULTI_INS_SETUP),multiInsSetupOpen)) multiInsSetupOpen=!multiInsSetupOpen; 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(); } pushToggleColors(newPatternRenderer); if (ImGui::SmallButton("NPR")) { newPatternRenderer=!newPatternRenderer; } ImGui::SetItemTooltip(_("New Pattern Renderer")); popToggleColors(); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PLAYBACK_STAT]); if (e->isPlaying() && settings.playbackTime) { TimeMicros totalTime=e->getCurTime(); 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 (totalTime.seconds==0x7fffffff) { info+=_("Don't you have anything better to do?"); } else { info+=totalTime.toString(2,TA_TIME_FORMAT_AUTO_MS_ZERO); } 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->newData[cursor.y][DIV_PAT_NOTE]>=0) { if (p->newData[cursor.y][DIV_PAT_NOTE]==DIV_NOTE_OFF) { info=fmt::sprintf(_("Note off (cut)")); } else if (p->newData[cursor.y][DIV_PAT_NOTE]==DIV_NOTE_REL) { info=fmt::sprintf(_("Note off (release)")); } else if (p->newData[cursor.y][DIV_PAT_NOTE]==DIV_MACRO_REL) { info=fmt::sprintf(_("Macro release only")); } else { info=fmt::sprintf(_("Note on: %s"),noteName(p->newData[cursor.y][DIV_PAT_NOTE])); } hasInfo=true; } break; case 1: // instrument if (p->newData[cursor.y][DIV_PAT_INS]>-1) { if (p->newData[cursor.y][DIV_PAT_INS]>=(int)e->song.ins.size()) { info=fmt::sprintf(_("Ins %d: "),p->newData[cursor.y][DIV_PAT_INS]); } else { DivInstrument* ins=e->getIns(p->newData[cursor.y][DIV_PAT_INS]); info=fmt::sprintf(_("Ins %d: %s"),p->newData[cursor.y][DIV_PAT_INS],ins->name); } hasInfo=true; } break; case 2: // volume if (p->newData[cursor.y][DIV_PAT_VOL]>-1) { int maxVol=e->getMaxVolumeChan(cursor.xCoarse); if (maxVol<1 || p->newData[cursor.y][DIV_PAT_VOL]>maxVol) { info=fmt::sprintf(_("Set volume: %d (%.2X, INVALID!)"),p->newData[cursor.y][DIV_PAT_VOL],p->newData[cursor.y][DIV_PAT_VOL]); } else { float realVol=e->getGain(cursor.xCoarse,p->newData[cursor.y][DIV_PAT_VOL]); info=fmt::sprintf(_("Set volume: %d (%.2X, %d%%)"),p->newData[cursor.y][DIV_PAT_VOL],p->newData[cursor.y][DIV_PAT_VOL],(int)(realVol*100.0f)); } hasInfo=true; } break; default: // effect if (cursor.xFinenewData[cursor.y][(cursor.xFine-1)|1]>-1) { info=e->getEffectDesc(p->newData[cursor.y][(cursor.xFine-1)|1],cursor.xCoarse,true); hasInfo=true; } } else { info=_("Error!"); 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()); updateKeyHitPre(); 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(refPlayer,drawRefPlayer()); MEASURE(multiInsSetup,drawMultiInsSetup()); MEASURE(patManager,drawPatManager()); MEASURE(tuner,drawTuner()); MEASURE(spectrum,drawSpectrum()); } 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(tuner,drawTuner()); MEASURE(spectrum,drawSpectrum()); MEASURE(channels,drawChannels()); MEASURE(patManager,drawPatManager()); MEASURE(sysManager,drawSysManager()); MEASURE(clock,drawClock()); MEASURE(regView,drawRegView()); MEASURE(log,drawLog()); MEASURE(effectList,drawEffectList()); MEASURE(userPresets,drawUserPresets()); MEASURE(refPlayer,drawRefPlayer()); MEASURE(multiInsSetup,drawMultiInsSetup()); } // release selection if mouse released if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) && selecting) { if (!selectingFull) cursor=selEnd; finishSelection(); if (!mobileUI) { // TODO: don't demand if selectingFull? 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); } } } } updateKeyHitPost(); 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; e->notifyInsChange(prevIns); } } } else { setCurIns(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_MUSIC_OPEN: workingDirMusic=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) { checkExtension(".wav"); } if (curFileDialog==GUI_FILE_EXPORT_AUDIO_ONE || curFileDialog==GUI_FILE_EXPORT_AUDIO_PER_SYS || curFileDialog==GUI_FILE_EXPORT_AUDIO_PER_CHANNEL) { if (audioExportFilterExt!="*") { checkExtension(audioExportFilterExt.c_str()); } } 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; notifySampleChange=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) { setCurIns(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; e->notifyInsChange(curIns); } 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_MUSIC_OPEN: e->synchronizedSoft([this,copyOfName]() { bool wasPlaying=e->getFilePlayer()->isPlaying(); if (!e->getFilePlayer()->loadFile(copyOfName.c_str())) { showError(fmt::sprintf(_("Error while loading file!"))); } else if (wasPlaying && filePlayerSync && refPlayerOpen && e->isPlaying()) { e->syncFilePlayer(); e->getFilePlayer()->play(); } }); 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...")); } int curFile=0; int* curFileLambda=&curFile; if (e->isExporting()) { e->lockEngine( [this, curFileLambda] () { *curFileLambda=0; e->getCurFileIndex(*curFileLambda); curProgress=(e->getCurTime().toDouble()+(songLength*(*curFileLambda)))/totalLength; } ); } if (curProgress<0.0f) curProgress=0.0f; if (curProgress>1.0f) curProgress=1.0f; 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_Borders)) { 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; recalcTimestamps=true; 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; recalcTimestamps=true; 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; recalcTimestamps=true; 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->newData,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short)); } }); MARK_MODIFIED; recalcTimestamps=true; ImGui::CloseCurrentPopup(); } if (ImGui::Button(_("Instruments"))) { stop(); e->lockEngine([this]() { e->song.clearInstruments(); }); setCurIns(-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; recalcTimestamps=true; ImGui::CloseCurrentPopup(); } if (ImGui::Button(_("Remove unused patterns"))) { stop(); e->lockEngine([this]() { e->curSubSong->removeUnusedPatterns(); }); MARK_MODIFIED; recalcTimestamps=true; 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; recalcTimestamps=true; 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(); recalcTimestamps=true; 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_NPR: if (ImGui::Button(_("Got it"))) { tutorial.nprFieldTrial=true; 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 setCurIns(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)); e->notifyInsChange(curIns); } 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; notifySampleChange=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); 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; }); } } } if (recalcTimestamps) { logV("need to recalc timestamps..."); e->calcSongTimestamps(); recalcTimestamps=false; } if (!e->isPlaying() && e->getFilePlayerSync()) { if (cursor.y!=prevCursor.y || cursor.order!=prevCursor.order) { DivFilePlayer* fp=e->getFilePlayer(); logV("cursor moved to %d:%d",cursor.order,cursor.y); if (!fp->isPlaying()) { TimeMicros rowTS=e->curSubSong->ts.getTimes(cursor.order,cursor.y); if (rowTS.seconds!=-1) { TimeMicros cueTime=e->getFilePlayerCue(); fp->setPosSeconds(cueTime+rowTS); } } } } 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; } } // reset chord count just in case chordInputOffset=0; } 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."); // new pattern renderer "field" trial. newPatternRenderer=(rand()&3); newFilePicker=new FurnaceFilePicker; newFilePicker->setConfigPrefix("fp_"); opTouched=new bool[DIV_MAX_PATTERNS*DIV_MAX_ROWS]; syncState(); syncSettings(); syncTutorial(); if (!tutorial.nprFieldTrial && newPatternRenderer) { showWarning(_("welcome to the New Pattern Renderer!\nit should be lighter on your CPU.\n\nif you find an issue, you can go back to the old pattern renderer by clicking the NPR button (next to Help).\nmake sure to report it!\n\nthank you!"),GUI_WARN_NPR); } 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(noteInputMode!=GUI_NOTE_INPUT_MONO); 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; //ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard; //ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NavCaptureKeyboard; 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); workingDirROM=e->getConfString("lastDirROM",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); workingDirMusic=e->getConfString("lastDirMusic",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); tunerOpen=e->getConfBool("tunerOpen",false); spectrumOpen=e->getConfBool("spectrumOpen",false); channelsOpen=e->getConfBool("channelsOpen",false); patManagerOpen=e->getConfBool("patManagerOpen",false); sysManagerOpen=e->getConfBool("sysManagerOpen",false); 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); refPlayerOpen=e->getConfBool("refPlayerOpen",false); multiInsSetupOpen=e->getConfBool("multiInsSetupOpen",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); editStep=e->getConfInt("editStep",1); editStepCoarse=e->getConfInt("editStepCoarse",16); noteInputMode=e->getConfInt("noteInputMode",GUI_NOTE_INPUT_POLY); if (noteInputMode!=GUI_NOTE_INPUT_MONO && noteInputMode!=GUI_NOTE_INPUT_POLY && noteInputMode!=GUI_NOTE_INPUT_CHORD) { noteInputMode=GUI_NOTE_INPUT_POLY; } filePlayerSync=e->getConfBool("filePlayerSync",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); spectrum.bins=e->getConfInt("spectrumBins",2048); spectrum.xZoom=e->getConfFloat("spectrumxZoom",1.0f); spectrum.xOffset=e->getConfFloat("spectrumxOffset",0); spectrum.yOffset=e->getConfFloat("spectrumyOffset",0); spectrum.mono=e->getConfBool("spectrumMono",false); pianoOctaves=e->getConfInt("pianoOctaves",pianoOctaves); pianoOctavesEdit=e->getConfInt("pianoOctavesEdit",pianoOctavesEdit); pianoOptions=e->getConfBool("pianoOptions",pianoOptions); 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); pianoLabelsMode=e->getConfInt("pianoLabelsMode",pianoLabelsMode); pianoKeyColorMode=e->getConfInt("pianoKeyColorMode",pianoKeyColorMode); chanOscCols=e->getConfInt("chanOscCols",3); chanOscAutoCols=e->getConfBool("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); chanOscColorMode=e->getConfInt("chanOscColorMode",0); 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); newFilePicker->loadSettings(e->getConfObject()); } 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("lastDirROM",workingDirROM); conf.set("lastDirFont",workingDirFont); conf.set("lastDirColors",workingDirColors); conf.set("lastDirKeybinds",workingDirKeybinds); conf.set("lastDirLayout",workingDirLayout); conf.set("lastDirConfig",workingDirConfig); conf.set("lastDirMusic",workingDirMusic); 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("tunerOpen",tunerOpen); conf.set("spectrumOpen",spectrumOpen); 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); conf.set("refPlayerOpen",refPlayerOpen); conf.set("multiInsSetupOpen",multiInsSetupOpen); // 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("editStep",editStep); conf.set("editStepCoarse",editStepCoarse); conf.set("orderEditMode",orderEditMode); conf.set("noteInputMode",(int)noteInputMode); conf.set("filePlayerSync",filePlayerSync); 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 spectrum state conf.set("spectrumBins",spectrum.bins); conf.set("spectrumxZoom",spectrum.xZoom); conf.set("spectrumxOffset",spectrum.xOffset); conf.set("spectrumyOffset",spectrum.yOffset); conf.set("spectrumMono",spectrum.mono); // commit piano state conf.set("pianoOctaves",pianoOctaves); conf.set("pianoOctavesEdit",pianoOctavesEdit); 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); conf.set("pianoLabelsMode",pianoLabelsMode); conf.set("pianoKeyColorMode",pianoKeyColorMode); // commit per-chan osc state conf.set("chanOscCols",chanOscCols); conf.set("chanOscAutoColsType",chanOscAutoCols); 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()); conf.set("chanOscColorMode",chanOscColorMode); // 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); newFilePicker->saveSettings(e->getConfObject()); } 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