diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 14c3d17a..41c7d294 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -347,6 +347,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { size_t len; if (slen<16) { logE("too small!"); + lastError="file is too small"; delete[] f; return false; } @@ -372,6 +373,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { } inflateEnd(&zl); delete[] f; + lastError="not a .dmf song"; return false; } @@ -385,8 +387,10 @@ bool DivEngine::load(unsigned char* f, size_t slen) { if (nextErr!=Z_OK && nextErr!=Z_STREAM_END) { if (zl.msg==NULL) { logE("zlib error: unknown error! %d\n",nextErr); + lastError="unknown decompression error"; } else { logE("zlib inflate: %s\n",zl.msg); + lastError=fmt::sprintf("decompression error: %s",zl.msg); } for (InflateBlock* i: blocks) delete i; blocks.clear(); @@ -405,8 +409,10 @@ bool DivEngine::load(unsigned char* f, size_t slen) { if (nextErr!=Z_OK) { if (zl.msg==NULL) { logE("zlib end error: unknown error! %d\n",nextErr); + lastError="unknown decompression finish error"; } else { logE("zlib end: %s\n",zl.msg); + lastError=fmt::sprintf("decompression finish error: %s",zl.msg); } for (InflateBlock* i: blocks) delete i; blocks.clear(); @@ -421,6 +427,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { } if (finalSize<1) { logE("compressed too small!\n"); + lastError="file too small"; for (InflateBlock* i: blocks) delete i; blocks.clear(); delete[] f; @@ -442,6 +449,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { } if (memcmp(file,DIV_DMF_MAGIC,16)!=0) { logE("not a valid module!\n"); + lastError="not a .dmf song"; delete[] file; return false; } @@ -456,11 +464,18 @@ bool DivEngine::load(unsigned char* f, size_t slen) { if (!reader.seek(16,SEEK_SET)) { logE("premature end of file!"); + lastError="incomplete file"; delete[] file; return false; } ds.version=reader.readC(); logI("module version %d (0x%.2x)\n",ds.version,ds.version); + if (ds.version>0x18) { + logW("this version is not supported by Furnace yet!\n"); + lastError="this version is not supported by Furnace yet"; + delete[] file; + return false; + } unsigned char sys=0; if (ds.version<0x09) { // V E R S I O N -> 3 <- @@ -472,6 +487,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { } if (ds.system==DIV_SYSTEM_NULL) { logE("invalid system 0x%.2x!",sys); + lastError="system not supported. running old version?"; delete[] file; return false; } @@ -574,6 +590,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { if (ins->mode) { // FM if (!isFMSystem(ds.system)) { logE("FM instrument in non-FM system. oopsie?\n"); + lastError="FM instrument in non-FM system"; delete[] file; return false; } @@ -595,6 +612,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { } if (ins->fm.ops!=2 && ins->fm.ops!=4) { logE("invalid op count %d. did we read it wrong?\n",ins->fm.ops); + lastError="file is corrupt or unreadable at operators"; delete[] file; return false; } @@ -762,6 +780,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { wave->len=(unsigned char)reader.readI(); if (wave->len>32) { logE("invalid wave length %d. are we doing something wrong?\n",wave->len); + lastError="file is corrupt or unreadable at wavetables"; delete[] file; return false; } @@ -788,6 +807,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { logD("%d fx rows: %d\n",i,chan.effectRows); if (chan.effectRows>4 || chan.effectRows<1) { logE("invalid effect row count %d. are you sure everything is ok?\n",chan.effectRows); + lastError="file is corrupt or unreadable at effect rows"; delete[] file; return false; } @@ -842,6 +862,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { sample->length=reader.readI(); if (sample->length<0) { logE("invalid sample length %d. are we doing something wrong?\n",sample->length); + lastError="file is corrupt or unreadable at samples"; delete[] file; return false; } @@ -897,6 +918,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { } } catch (EndOfFileException e) { logE("premature end of file!\n"); + lastError="incomplete file"; delete[] file; return false; } @@ -1373,6 +1395,10 @@ void DivEngine::changeSystem(DivSystem which) { isBusy.unlock(); } +String DivEngine::getLastError() { + return lastError; +} + DivInstrument* DivEngine::getIns(int index) { if (index<0 || index>=song.insLen) return &song.nullIns; return song.ins[index]; diff --git a/src/engine/engine.h b/src/engine/engine.h index 3d3c955f..0f858451 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -89,6 +89,7 @@ class DivEngine { std::mutex isBusy; String configPath; String configFile; + String lastError; short vibTable[64]; @@ -260,6 +261,9 @@ class DivEngine { // change system void changeSystem(DivSystem which); + // get last error + String getLastError(); + // init dispatch void initDispatch(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 8b9ce222..84aee071 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -355,9 +355,18 @@ void FurnaceGUI::drawOrders() { char selID[16]; if (!ordersOpen) return; if (ImGui::Begin("Orders",&ordersOpen)) { + float regionX=ImGui::GetContentRegionAvail().x; + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(1.0f*dpiScale,1.0f*dpiScale)); + ImGui::Columns(2,NULL,false); + ImGui::SetColumnWidth(-1,regionX-16.0f*dpiScale); if (ImGui::BeginTable("OrdersTable",1+e->getChannelCount(e->song.system),ImGuiTableFlags_ScrollY)) { + ImGui::PushFont(patFont); ImGui::TableSetupScrollFreeze(0,1); - ImGui::TableNextRow(); + float lineHeight=(ImGui::GetTextLineHeight()+4*dpiScale); + if (e->isPlaying()) { + ImGui::SetScrollY((e->getOrder()+1)*lineHeight-(ImGui::GetContentRegionAvail().y/2)); + } + ImGui::TableNextRow(0,lineHeight); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_ROW_INDEX]); for (int i=0; igetChannelCount(e->song.system); i++) { @@ -366,8 +375,8 @@ void FurnaceGUI::drawOrders() { } ImGui::PopStyleColor(); for (int i=0; isong.ordersLen; i++) { - ImGui::TableNextRow(); - if (e->getOrder()==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,0x40ffffff); + ImGui::TableNextRow(0,lineHeight); + if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,0x40ffffff); ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_ROW_INDEX]); snprintf(selID,16,"%.2x##O_S%.2x",i,i); @@ -394,11 +403,20 @@ void FurnaceGUI::drawOrders() { } } } - + ImGui::PopFont(); ImGui::EndTable(); } + ImGui::NextColumn(); + ImGui::Button("N"); + ImGui::Button("R"); + ImGui::Button("C"); + ImGui::Button("U"); + ImGui::Button("D"); + ImGui::Button("d"); + ImGui::PopStyleVar(); } if (ImGui::IsWindowFocused()) curWindow=GUI_WINDOW_ORDERS; + oldOrder1=e->getOrder(); ImGui::End(); } @@ -920,7 +938,8 @@ void FurnaceGUI::drawPattern() { ImGui::SetWindowSize(ImVec2(scrW*dpiScale,scrH*dpiScale)); char id[32]; ImGui::PushFont(patFont); - unsigned char ord=e->getOrder(); + unsigned char ord=e->isPlaying()?oldOrder:e->getOrder(); + oldOrder=e->getOrder(); int chans=e->getChannelCount(e->song.system); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f)); if (ImGui::BeginTable("PatternView",chans+2,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY|ImGuiTableFlags_NoPadInnerX)) { @@ -939,13 +958,13 @@ void FurnaceGUI::drawPattern() { } ImGui::TableNextRow(); ImGui::TableNextColumn(); - if (ImGui::Selectable(extraChannelButtons?" --##ExtraChannelButtons":" ++##ExtraChannelButtons",false,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+2.0f*dpiScale))) { + if (ImGui::Selectable(extraChannelButtons?" --##ExtraChannelButtons":" ++##ExtraChannelButtons",false,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale))) { extraChannelButtons=!extraChannelButtons; } for (int i=0; igetChannelName(i),i); - if (ImGui::Selectable(chanID,!e->isChannelMuted(i),ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+2.0f*dpiScale))) { + if (ImGui::Selectable(chanID,!e->isChannelMuted(i),ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale))) { e->toggleMute(i); } if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { @@ -968,6 +987,7 @@ void FurnaceGUI::drawPattern() { if (e->song.pat[i].effectRows>4) e->song.pat[i].effectRows=4; } ImGui::EndDisabled(); + ImGui::Spacing(); } } ImGui::TableNextColumn(); @@ -1746,6 +1766,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { int FurnaceGUI::save(String path) { FILE* outFile=fopen(path.c_str(),"wb"); if (outFile==NULL) { + lastError=strerror(errno); return 1; } SafeWriter* w=e->save(); @@ -1757,6 +1778,7 @@ int FurnaceGUI::save(String path) { ret=deflateInit(&zl,Z_DEFAULT_COMPRESSION); if (ret!=Z_OK) { logE("zlib error!\n"); + lastError="compression error"; fclose(outFile); w->finish(); return 2; @@ -1768,6 +1790,7 @@ int FurnaceGUI::save(String path) { zl.next_out=zbuf; if ((ret=deflate(&zl,Z_NO_FLUSH))==Z_STREAM_ERROR) { logE("zlib stream error!\n"); + lastError="zlib stream error"; deflateEnd(&zl); fclose(outFile); w->finish(); @@ -1777,6 +1800,7 @@ int FurnaceGUI::save(String path) { if (amount>0) { if (fwrite(zbuf,1,amount,outFile)!=amount) { logE("did not write entirely: %s!\n",strerror(errno)); + lastError=strerror(errno); deflateEnd(&zl); fclose(outFile); w->finish(); @@ -1788,6 +1812,7 @@ int FurnaceGUI::save(String path) { zl.next_out=zbuf; if ((ret=deflate(&zl,Z_FINISH))==Z_STREAM_ERROR) { logE("zlib finish stream error!\n"); + lastError="zlib finish stream error"; deflateEnd(&zl); fclose(outFile); w->finish(); @@ -1796,6 +1821,7 @@ int FurnaceGUI::save(String path) { 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!\n",strerror(errno)); + lastError=strerror(errno); deflateEnd(&zl); fclose(outFile); w->finish(); @@ -1806,6 +1832,7 @@ int FurnaceGUI::save(String path) { #else if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { logE("did not write entirely: %s!\n",strerror(errno)); + lastError=strerror(errno); fclose(outFile); w->finish(); return 1; @@ -1822,24 +1849,29 @@ int FurnaceGUI::load(String path) { FILE* f=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==0x7fffffffffffffff) { 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) { printf("that file is empty!\n"); + lastError="file is empty"; } else { perror("tell error"); + lastError=fmt::sprintf("on tell: %s",strerror(errno)); } fclose(f); return 1; @@ -1847,26 +1879,35 @@ int FurnaceGUI::load(String path) { unsigned char* file=new unsigned char[len]; if (fseek(f,0,SEEK_SET)<0) { perror("size error"); + lastError=fmt::sprintf("on get size: %s",strerror(errno)); fclose(f); delete[] file; return 1; } if (fread(file,1,(size_t)len,f)!=(size_t)len) { perror("read error"); + lastError=fmt::sprintf("on read: %s",strerror(errno)); fclose(f); delete[] file; return 1; } fclose(f); if (!e->load(file,(size_t)len)) { + lastError=e->getLastError(); logE("could not open file!\n"); return 1; } } + lastError="everything OK"; updateWindowTitle(); return 0; } +void FurnaceGUI::showError(String what) { + errorString=what; + ImGui::OpenPopup("Error"); +} + #define sysChangeOption(x) \ if (ImGui::MenuItem(e->getSystemName(x),NULL,e->song.system==x)) { \ e->changeSystem(x); \ @@ -2059,11 +2100,15 @@ bool FurnaceGUI::loop() { String copyOfName=fileName; switch (curFileDialog) { case GUI_FILE_OPEN: - load(copyOfName); + if (load(copyOfName)>0) { + showError(fmt::sprintf("Error while loading file! (%s)",lastError)); + } break; case GUI_FILE_SAVE: printf("saving: %s\n",copyOfName.c_str()); - save(copyOfName); + if (save(copyOfName)>0) { + showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + } break; case GUI_FILE_SAMPLE_OPEN: e->addSampleFromFile(copyOfName.c_str()); @@ -2088,6 +2133,14 @@ bool FurnaceGUI::loop() { if (aboutOpen) drawAbout(); + if (ImGui::BeginPopupModal("Error",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("%s",errorString.c_str()); + if (ImGui::Button("OK")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + SDL_SetRenderDrawColor(sdlRend,uiColors[GUI_COLOR_BACKGROUND].x*255, uiColors[GUI_COLOR_BACKGROUND].y*255, uiColors[GUI_COLOR_BACKGROUND].z*255, @@ -2207,6 +2260,8 @@ FurnaceGUI::FurnaceGUI(): curSample(0), curOctave(3), oldRow(0), + oldOrder(0), + oldOrder1(0), editStep(1), editControlsOpen(true), ordersOpen(true), diff --git a/src/gui/gui.h b/src/gui/gui.h index 1d5aac88..00b3ad95 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -68,7 +68,7 @@ class FurnaceGUI { SDL_Window* sdlWin; SDL_Renderer* sdlRend; - String workingDir, fileName, clipboard; + String workingDir, fileName, clipboard, errorString, lastError; bool quit, willCommit; @@ -91,7 +91,7 @@ class FurnaceGUI { char finalLayoutPath[4096]; - int curIns, curWave, curSample, curOctave, oldRow, editStep; + int curIns, curWave, curSample, curOctave, oldRow, oldOrder, oldOrder1, editStep; bool editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen; bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; SelectionPoint selStart, selEnd; @@ -162,6 +162,8 @@ class FurnaceGUI { int save(String path); int load(String path); + void showError(String what); + public: const char* noteName(short note, short octave); bool decodeNote(const char* what, short& note, short& octave);