diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ec8ed890..e2d5aef2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3.1.0 with: submodules: recursive @@ -59,9 +59,9 @@ jobs: echo "MinGW cross target: ${mingw_target}" fi - echo "::set-output name=vswhere-target::${vswhere_target}" - echo "::set-output name=msvc-target::${msvc_target}" - echo "::set-output name=mingw-target::${mingw_target}" + echo "vswhere-target=${vswhere_target}" >> $GITHUB_OUTPUT + echo "msvc-target=${msvc_target}" >> $GITHUB_OUTPUT + echo "mingw-target=${mingw_target}" >> $GITHUB_OUTPUT - name: Set package identifier id: package-identify @@ -88,8 +88,8 @@ jobs: echo "Package identifier: ${package_name}" echo "Package file: ${package_name}${package_ext}" - echo "::set-output name=id::${package_name}" - echo "::set-output name=filename::${package_name}${package_ext}" + echo "id=${package_name}" >> $GITHUB_OUTPUT + echo "filename=${package_name}${package_ext}" >> $GITHUB_OUTPUT - name: Set build cores amount id: build-cores @@ -102,11 +102,11 @@ jobs: echo "Amount of cores we can build with: ${amount}" - echo "::set-output name=amount::${amount}" + echo "amount=${amount}" >> $GITHUB_OUTPUT - name: Setup Toolchain [Windows MSVC] if: ${{ matrix.config.compiler == 'msvc' }} - uses: seanmiddleditch/gha-setup-vsdevenv@v3 + uses: vadz/gha-setup-vsdevenv@avoid-deprecation-warnings with: arch: ${{ steps.windows-identify.outputs.vswhere-target }} @@ -314,7 +314,7 @@ jobs: - name: Upload artifact if: ${{ github.repository == 'tildearrow/furnace' && github.ref_name == 'master' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v3.1.1 with: name: ${{ steps.package-identify.outputs.id }} path: ${{ steps.package-identify.outputs.filename }} diff --git a/README.md b/README.md index 6c62768a..000c6610 100644 --- a/README.md +++ b/README.md @@ -85,9 +85,6 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a - available on wavetable chips - create complex sounds with ease - provide up to two wavetables, select and effect and let go! - MIDI input support -- [Fractal Sound](https://gitlab.com/Natsumi/Fractal-Sound) support! - - the game-ready Sega Genesis/Mega Drive sound driver! - - compose your songs in Furnace using the Fractal Sound presets and then use them in your games with Fractal! - additional features: - FM macros! - negative octaves diff --git a/android/app/src/main/java/org/tildearrow/furnace/MainActivity.java b/android/app/src/main/java/org/tildearrow/furnace/MainActivity.java index b7a39aa5..0ac1448f 100644 --- a/android/app/src/main/java/org/tildearrow/furnace/MainActivity.java +++ b/android/app/src/main/java/org/tildearrow/furnace/MainActivity.java @@ -1,7 +1,25 @@ package org.tildearrow.furnace; +import android.content.Intent; + import org.libsdl.app.SDLActivity; -public class MainActivity extends SDLActivity -{ +public class MainActivity extends SDLActivity { + static final int TA_FILE_REQUEST=1000; + + public boolean showFileDialog() { + Intent picker=new Intent(Intent.ACTION_GET_CONTENT); + picker.setType("*/*"); + picker=Intent.createChooser(picker,"test"); + startActivityForResult(picker,TA_FILE_REQUEST); + + return true; + } + + @Override protected void onActivityResult(int request, int result, Intent intent) { + super.onActivityResult(request,result,intent); + if (request==TA_FILE_REQUEST) { + // TODO: fire an event here + } + } } diff --git a/demos/ComicPartytrack20.fur b/demos/ComicPartytrack20.fur new file mode 100644 index 00000000..61a6aee5 Binary files /dev/null and b/demos/ComicPartytrack20.fur differ diff --git a/demos/L’ambreSong.fur b/demos/L’ambreSong.fur new file mode 100644 index 00000000..7ddd5401 Binary files /dev/null and b/demos/L’ambreSong.fur differ diff --git a/demos/MMXStageClear.fur b/demos/MMXStageClear.fur new file mode 100644 index 00000000..727e7f79 Binary files /dev/null and b/demos/MMXStageClear.fur differ diff --git a/papers/doc/7-systems/ym2151.md b/papers/doc/7-systems/ym2151.md index 60764d19..fc297f2e 100644 --- a/papers/doc/7-systems/ym2151.md +++ b/papers/doc/7-systems/ym2151.md @@ -28,6 +28,8 @@ in most arcade boards the chip was used in combination with a PCM chip, like [Se - `1Bxx`: set attack of operator 2. - `1Cxx`: set attack of operator 3. - `1Dxx`: set attack of operator 4. +- `1Exx`: set LFO AM depth. +- `1Fxx`: set LFO PM depth. - `30xx`: enable envelope hard reset. - this works by inserting a quick release and tiny delay before a new note. - `50xy`: set AM of operator. diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 3794ff91..1887e5b0 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2438,6 +2438,29 @@ int DivEngine::getEffectiveSampleRate(int rate) { void DivEngine::previewSample(int sample, int note, int pStart, int pEnd) { BUSY_BEGIN; + previewSampleNoLock(sample,note,pStart,pEnd); + BUSY_END; +} + +void DivEngine::stopSamplePreview() { + BUSY_BEGIN; + stopSamplePreviewNoLock(); + BUSY_END; +} + +void DivEngine::previewWave(int wave, int note) { + BUSY_BEGIN; + previewWaveNoLock(wave,note); + BUSY_END; +} + +void DivEngine::stopWavePreview() { + BUSY_BEGIN; + stopWavePreviewNoLock(); + BUSY_END; +} + +void DivEngine::previewSampleNoLock(int sample, int note, int pStart, int pEnd) { sPreview.pBegin=pStart; sPreview.pEnd=pEnd; sPreview.dir=false; @@ -2445,7 +2468,6 @@ void DivEngine::previewSample(int sample, int note, int pStart, int pEnd) { sPreview.sample=-1; sPreview.pos=0; sPreview.dir=false; - BUSY_END; return; } blip_clear(samp_bb); @@ -2462,28 +2484,22 @@ void DivEngine::previewSample(int sample, int note, int pStart, int pEnd) { sPreview.sample=sample; sPreview.wave=-1; sPreview.dir=false; - BUSY_END; } -void DivEngine::stopSamplePreview() { - BUSY_BEGIN; +void DivEngine::stopSamplePreviewNoLock() { sPreview.sample=-1; sPreview.pos=0; sPreview.dir=false; - BUSY_END; } -void DivEngine::previewWave(int wave, int note) { - BUSY_BEGIN; +void DivEngine::previewWaveNoLock(int wave, int note) { if (wave<0 || wave>=(int)song.wave.size()) { sPreview.wave=-1; sPreview.pos=0; sPreview.dir=false; - BUSY_END; return; } if (song.wave[wave]->len<=0) { - BUSY_END; return; } blip_clear(samp_bb); @@ -2496,15 +2512,12 @@ void DivEngine::previewWave(int wave, int note) { sPreview.sample=-1; sPreview.wave=wave; sPreview.dir=false; - BUSY_END; } -void DivEngine::stopWavePreview() { - BUSY_BEGIN; +void DivEngine::stopWavePreviewNoLock() { sPreview.wave=-1; sPreview.pos=0; sPreview.dir=false; - BUSY_END; } bool DivEngine::isPreviewingSample() { diff --git a/src/engine/engine.h b/src/engine/engine.h index ccb0f6d7..510ef23a 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -616,6 +616,14 @@ class DivEngine { void previewWave(int wave, int note); void stopWavePreview(); + // trigger sample preview + void previewSampleNoLock(int sample, int note=-1, int pStart=-1, int pEnd=-1); + void stopSamplePreviewNoLock(); + + // trigger wave preview + void previewWaveNoLock(int wave, int note); + void stopWavePreviewNoLock(); + // get config path String getConfigPath(); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 4b6e2f75..fdc216ac 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1412,7 +1412,7 @@ void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivS newFlags.set("chipType",0); break; case 1: - newFlags.set("chipType",0); + newFlags.set("chipType",1); break; } break; diff --git a/src/engine/waveSynth.cpp b/src/engine/waveSynth.cpp index 82b5d5dd..e68e3675 100644 --- a/src/engine/waveSynth.cpp +++ b/src/engine/waveSynth.cpp @@ -60,7 +60,7 @@ bool DivWaveSynth::tick(bool skipSubDiv) { break; case DIV_WS_SUBTRACT: for (int i=0; i<=state.speed; i++) { - output[pos]+=MIN(height,state.param1); + output[pos]-=MIN(height,state.param1); if (output[pos]<0) output[pos]+=height; if (++pos>=width) pos=0; } diff --git a/src/gui/about.cpp b/src/gui/about.cpp index f2fb5029..d5bac49e 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -104,6 +104,7 @@ const char* aboutLine[]={ "fd", "GENATARi", "host12prog", + "Lumigado", "Lunathir", "plane", "TheEssem", diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index f45028ff..8a62c822 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -410,7 +410,7 @@ void FurnaceGUI::drawInsList(bool asChild) { curIns=i; wavePreviewInit=true; } - if (ImGui::IsItemHovered() && i>=0) { + if (ImGui::IsItemHovered() && i>=0 && !mobileUI) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); ImGui::SetTooltip("%s",insType); ImGui::PopStyleColor(); @@ -679,7 +679,7 @@ void FurnaceGUI::actualSampleList() { updateSampleTex=true; } if (wantScrollList && curSample==i) ImGui::SetScrollHereY(); - if (ImGui::IsItemHovered()) { + if (ImGui::IsItemHovered() && !mobileUI) { ImGui::SetTooltip("Bank %d: %s",i/12,sampleNote[i%12]); if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { sampleEditOpen=true; diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index e3e348c1..86a7325e 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -244,6 +244,14 @@ void FurnaceGUI::drawMobileControls() { mobileMenuOpen=false; } ImGui::SameLine(); + if (ImGui::Button("Log")) { + logOpen=!logOpen; + } + ImGui::SameLine(); + if (ImGui::Button("Debug")) { + debugOpen=!debugOpen; + } + ImGui::SameLine(); if (ImGui::Button("About")) { mobileMenuOpen=false; mobileMenuPos=0.0f; diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index 8d524ab4..c5103019 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -4,6 +4,8 @@ #ifdef USE_NFD #include +#elif defined(ANDROID) +#include #else #include "../../extern/pfd-fixed/portable-file-dialogs.h" #endif @@ -87,6 +89,45 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, c #else dialogO=new std::thread(_nfdThread,NFDState(false,header,filter,path,clickCallback,allowMultiple),&dialogOK,&nfdResult,&hasError); #endif +#elif defined(ANDROID) + hasError=false; + if (jniEnv==NULL) { + jniEnv=(JNIEnv*)SDL_AndroidGetJNIEnv(); + if (jniEnv==NULL) { + hasError=true; + logE("could not acquire JNI env!"); + return false; + } + } + + jobject activity=(jobject)SDL_AndroidGetActivity(); + if (activity==NULL) { + hasError=true; + logE("the Activity is NULL!"); + return false; + } + + jclass class_=jniEnv->GetObjectClass(activity); + jmethodID showFileDialog=jniEnv->GetMethodID(class_,"showFileDialog","()B"); + + if (showFileDialog==NULL) { + logE("method showFileDialog not found!"); + hasError=true; + jniEnv->DeleteLocalRef(class_); + jniEnv->DeleteLocalRef(activity); + return false; + } + + jboolean mret=jniEnv->CallBooleanMethod(activity,showFileDialog); + + if (!(bool)mret) { + hasError=true; + logW("could not open Android file picker..."); + } + + jniEnv->DeleteLocalRef(class_); + jniEnv->DeleteLocalRef(activity); + return true; #else dialogO=new pfd::open_file(header,path,filter,allowMultiple?(pfd::opt::multiselect):(pfd::opt::none)); hasError=!pfd::settings::available(); @@ -113,6 +154,8 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, c #else dialogS=new std::thread(_nfdThread,NFDState(true,header,filter,path,NULL,false),&dialogOK,&nfdResult,&hasError); #endif +#elif defined(ANDROID) + hasError=true; // TODO #else dialogS=new pfd::save_file(header,path,filter); hasError=!pfd::settings::available(); @@ -141,7 +184,9 @@ void FurnaceGUIFileDialog::close() { #ifdef USE_NFD dialogS->join(); #endif +#ifndef ANDROID delete dialogS; +#endif dialogS=NULL; } } else { @@ -149,7 +194,9 @@ void FurnaceGUIFileDialog::close() { #ifdef USE_NFD dialogO->join(); #endif +#ifndef ANDROID delete dialogO; +#endif dialogO=NULL; } } @@ -179,6 +226,9 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { return true; } return false; +#elif defined(ANDROID) + // TODO: detect when file picker is closed + return false; #else if (saving) { if (dialogS!=NULL) { diff --git a/src/gui/fileDialog.h b/src/gui/fileDialog.h index 7990b037..0fbeeb46 100644 --- a/src/gui/fileDialog.h +++ b/src/gui/fileDialog.h @@ -15,6 +15,8 @@ #define NFD_NON_THREADED #endif +#elif defined(ANDROID) +#include #else namespace pfd { class open_file; @@ -36,6 +38,10 @@ class FurnaceGUIFileDialog { std::thread* dialogS; std::atomic dialogOK; std::vector nfdResult; +#elif defined(ANDROID) + JNIEnv* jniEnv; + void* dialogO; + void* dialogS; #else pfd::open_file* dialogO; pfd::save_file* dialogS; @@ -55,6 +61,9 @@ class FurnaceGUIFileDialog { opened(false), saving(false), hasError(false), +#ifdef ANDROID + jniEnv(NULL), +#endif dialogO(NULL), dialogS(NULL) {} }; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e8bfb48b..30cb0850 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2977,6 +2977,10 @@ bool FurnaceGUI::loop() { } 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); @@ -3039,6 +3043,9 @@ bool FurnaceGUI::loop() { if (!ImGui::GetIO().WantCaptureKeyboard) { keyDown(ev); } +#ifdef IS_MOBILE + injectBackUp=true; +#endif break; case SDL_KEYUP: // for now @@ -3738,6 +3745,10 @@ bool FurnaceGUI::loop() { drawPiano(); break; } + + globalWinFlags=0; + drawDebug(); + drawLog(); } else { globalWinFlags=0; ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoWindowMenuButton|ImGuiDockNodeFlags_NoMove|ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0); @@ -3808,7 +3819,7 @@ bool FurnaceGUI::loop() { } #endif - if (fileDialog->render(ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(canvasW,canvasH))) { + 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) { @@ -3896,8 +3907,12 @@ bool FurnaceGUI::loop() { 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()) { @@ -4920,6 +4935,8 @@ bool FurnaceGUI::loop() { } } } + + curWindowThreadSafe=curWindow; SDL_SetRenderDrawColor(sdlRend,uiColors[GUI_COLOR_BACKGROUND].x*255, uiColors[GUI_COLOR_BACKGROUND].y*255, @@ -5026,6 +5043,10 @@ bool FurnaceGUI::init() { followOrders=e->getConfBool("followOrders",true); followPattern=e->getConfBool("followPattern",true); noteInputPoly=e->getConfBool("noteInputPoly",true); + exportLoops=e->getConfInt("exportLoops",0); + if (exportLoops<0) exportLoops=0; + exportFadeOut=e->getConfDouble("exportFadeOut",0.0); + if (exportFadeOut<0.0) exportFadeOut=0.0; orderEditMode=e->getConfInt("orderEditMode",0); if (orderEditMode<0) orderEditMode=0; if (orderEditMode>3) orderEditMode=3; @@ -5060,6 +5081,11 @@ bool FurnaceGUI::init() { syncSettings(); + if (!settings.persistFadeOut) { + exportLoops=settings.exportLoops; + exportFadeOut=settings.exportFadeOut; + } + for (int i=0; igetConfString(fmt::sprintf("recentFile%d",i),""); if (!r.empty()) { @@ -5276,6 +5302,31 @@ bool FurnaceGUI::init() { if (!midiMap.noteInput) return -2; if (learning!=-1) return -2; if (midiMap.at(msg)) return -2; + + if (curWindowThreadSafe==GUI_WINDOW_WAVE_EDIT || curWindowThreadSafe==GUI_WINDOW_WAVE_LIST) { + if ((msg.type&0xf0)==TA_MIDI_NOTE_ON) { + e->previewWaveNoLock(curWave,msg.data[0]-12); + wavePreviewNote=msg.data[0]-12; + } else if ((msg.type&0xf0)==TA_MIDI_NOTE_OFF) { + if (wavePreviewNote==msg.data[0]-12) { + e->stopWavePreviewNoLock(); + } + } + return -2; + } + + if (curWindowThreadSafe==GUI_WINDOW_SAMPLE_EDIT || curWindowThreadSafe==GUI_WINDOW_SAMPLE_LIST) { + if ((msg.type&0xf0)==TA_MIDI_NOTE_ON) { + e->previewSampleNoLock(curSample,msg.data[0]-12); + samplePreviewNote=msg.data[0]-12; + } else if ((msg.type&0xf0)==TA_MIDI_NOTE_OFF) { + if (samplePreviewNote==msg.data[0]-12) { + e->stopSamplePreviewNoLock(); + } + } + return -2; + } + return curIns; }); @@ -5359,6 +5410,10 @@ bool FurnaceGUI::finish() { e->setConf("followPattern",followPattern); e->setConf("orderEditMode",orderEditMode); e->setConf("noteInputPoly",noteInputPoly); + if (settings.persistFadeOut) { + e->setConf("exportLoops",exportLoops); + e->setConf("exportFadeOut",exportFadeOut); + } // commit oscilloscope state e->setConf("oscZoom",oscZoom); @@ -5431,6 +5486,7 @@ FurnaceGUI::FurnaceGUI(): vgmExportPatternHints(false), vgmExportDirectStream(false), portrait(false), + injectBackUp(false), mobileMenuOpen(false), wantCaptureKeyboard(false), oldWantCaptureKeyboard(false), @@ -5443,6 +5499,7 @@ FurnaceGUI::FurnaceGUI(): displayPendingIns(false), pendingInsSingle(false), displayPendingRawSample(false), + snesFilterHex(false), vgmExportVersion(0x171), drawHalt(10), zsmExportTickRate(60), @@ -5572,6 +5629,7 @@ FurnaceGUI::FurnaceGUI(): curWindow(GUI_WINDOW_NOTHING), nextWindow(GUI_WINDOW_NOTHING), curWindowLast(GUI_WINDOW_NOTHING), + curWindowThreadSafe(GUI_WINDOW_NOTHING), lastPatternWidth(0.0f), nextDesc(NULL), latchNote(-1), diff --git a/src/gui/gui.h b/src/gui/gui.h index dd3a02af..e1672a8e 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1028,7 +1028,7 @@ class FurnaceGUI { String workingDirVGMExport, workingDirZSMExport, workingDirROMExport, workingDirFont, workingDirColors, workingDirKeybinds; String workingDirLayout, workingDirROM, workingDirTest; String mmlString[32]; - String mmlStringW; + String mmlStringW, mmlStringSNES; std::vector sysSearchResults; std::vector newSongSearchResults; @@ -1036,10 +1036,10 @@ class FurnaceGUI { bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, zsmExportLoop, vgmExportPatternHints; bool vgmExportDirectStream; - bool portrait, mobileMenuOpen; + bool portrait, injectBackUp, mobileMenuOpen; bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly; - bool displayPendingIns, pendingInsSingle, displayPendingRawSample; + bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex; bool willExport[32]; int vgmExportVersion; int drawHalt; @@ -1212,6 +1212,10 @@ class FurnaceGUI { int midiOutMode; int maxRecentFile; int centerPattern; + int ordersCursor; + int persistFadeOut; + int exportLoops; + double exportFadeOut; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -1255,7 +1259,7 @@ class FurnaceGUI { wrapVertical(0), macroView(0), fmNames(0), - allowEditDocking(0), + allowEditDocking(1), chipNames(0), overflowHighlight(0), partyTime(0), @@ -1338,6 +1342,10 @@ class FurnaceGUI { midiOutMode(1), maxRecentFile(10), centerPattern(0), + ordersCursor(1), + persistFadeOut(1), + exportLoops(0), + exportFadeOut(0.0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -1374,6 +1382,7 @@ class FurnaceGUI { bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, waveSigned, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; bool keepLoopAlive; FurnaceGUIWindows curWindow, nextWindow, curWindowLast; + std::atomic curWindowThreadSafe; float peak[2]; float patChanX[DIV_MAX_CHANS+1]; float patChanSlideY[DIV_MAX_CHANS+1]; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 6a8faf78..99a87aeb 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1852,6 +1852,11 @@ void FurnaceGUI::drawMacros(std::vector& macros) { #define CENTER_TEXT_20(text) \ ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(20.0f*dpiScale-ImGui::CalcTextSize(text).x)); +#define TOOLTIP_TEXT(text) \ + if (ImGui::IsItemHovered()) { \ + ImGui::SetTooltip("%s", text); \ + } + #define OP_DRAG_POINT \ if (ImGui::Button(ICON_FA_ARROWS)) { \ } \ @@ -2329,69 +2334,85 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_AR)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_AR)); + TOOLTIP_TEXT(FM_NAME(FM_AR)); ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_DR)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_DR)); + TOOLTIP_TEXT(FM_NAME(FM_DR)); if (settings.susPosition==0) { ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_SL)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_SL)); + TOOLTIP_TEXT(FM_NAME(FM_SL)); } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_D2R)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_D2R)); + TOOLTIP_TEXT(FM_NAME(FM_D2R)); } ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_RR)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_RR)); + TOOLTIP_TEXT(FM_NAME(FM_RR)); if (settings.susPosition==1) { ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_SL)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_SL)); + TOOLTIP_TEXT(FM_NAME(FM_SL)); } ImGui::TableNextColumn(); ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_TL)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_TL)); + TOOLTIP_TEXT(FM_NAME(FM_TL)); ImGui::TableNextColumn(); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { CENTER_TEXT(FM_SHORT_NAME(FM_RS)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_RS)); + TOOLTIP_TEXT(FM_NAME(FM_RS)); } else { CENTER_TEXT(FM_SHORT_NAME(FM_KSL)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_KSL)); + TOOLTIP_TEXT(FM_NAME(FM_KSL)); } if (ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_EGSHIFT)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_EGSHIFT)); + TOOLTIP_TEXT(FM_NAME(FM_EGSHIFT)); ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_REV)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_REV)); + TOOLTIP_TEXT(FM_NAME(FM_REV)); } ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_MULT)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_MULT)); + TOOLTIP_TEXT(FM_NAME(FM_MULT)); if (ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_FINE)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_FINE)); + TOOLTIP_TEXT(FM_NAME(FM_FINE)); } ImGui::TableNextColumn(); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { CENTER_TEXT(FM_SHORT_NAME(FM_DT)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT)); + TOOLTIP_TEXT(FM_NAME(FM_DT)); ImGui::TableNextColumn(); } if (ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { CENTER_TEXT(FM_SHORT_NAME(FM_DT2)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT2)); + TOOLTIP_TEXT(FM_NAME(FM_DT2)); ImGui::TableNextColumn(); } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM) { CENTER_TEXT(FM_SHORT_NAME(FM_AM)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_AM)); + TOOLTIP_TEXT(FM_NAME(FM_AM)); } else { CENTER_TEXT("Other"); ImGui::TextUnformatted("Other"); @@ -2703,7 +2724,7 @@ void FurnaceGUI::drawInsEdit() { char tempID[1024]; ImVec2 oldPadding=ImGui::GetStyle().CellPadding; ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(8.0f*dpiScale,4.0f*dpiScale)); - if (ImGui::BeginTable("KGE93BSIEO3NOWBDJZBA",columns,ImGuiTableFlags_SizingStretchSame|ImGuiTableFlags_BordersInner)) { + if (ImGui::BeginTable("AltFMOperators",columns,ImGuiTableFlags_SizingStretchSame|ImGuiTableFlags_BordersInner)) { for (int i=0; ifm.op[(opCount==4 && ins->type!=DIV_INS_OPL_DRUMS)?opOrder[i]:i]; if ((settings.fmLayout!=6 && ((i+1)&1)) || i==0 || settings.fmLayout==5) ImGui::TableNextRow(); @@ -2804,6 +2825,7 @@ void FurnaceGUI::drawInsEdit() { float textY=ImGui::GetCursorPosY(); CENTER_TEXT_20(FM_SHORT_NAME(FM_AR)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_AR)); + TOOLTIP_TEXT(FM_NAME(FM_AR)); ImGui::TableNextColumn(); if (ins->type==DIV_INS_FM) { ImGui::Text("SSG-EG"); @@ -2814,7 +2836,8 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("Envelope"); ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_TL)); - ImGui::Text("TL"); + ImGui::Text(FM_SHORT_NAME(FM_TL)); + TOOLTIP_TEXT(FM_NAME(FM_TL)); // A/D/S/R ImGui::TableNextColumn(); @@ -2861,19 +2884,23 @@ void FurnaceGUI::drawInsEdit() { ImGui::SetCursorPos(ImVec2(textX_DR,textY)); CENTER_TEXT_20(FM_SHORT_NAME(FM_DR)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_DR)); + TOOLTIP_TEXT(FM_NAME(FM_DR)); ImGui::SetCursorPos(ImVec2(textX_SL,textY)); CENTER_TEXT_20(FM_SHORT_NAME(FM_SL)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_SL)); + TOOLTIP_TEXT(FM_NAME(FM_SL)); ImGui::SetCursorPos(ImVec2(textX_RR,textY)); CENTER_TEXT_20(FM_SHORT_NAME(FM_RR)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_RR)); + TOOLTIP_TEXT(FM_NAME(FM_RR)); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) { ImGui::SetCursorPos(ImVec2(textX_D2R,textY)); CENTER_TEXT_20(FM_SHORT_NAME(FM_D2R)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_D2R)); + TOOLTIP_TEXT(FM_NAME(FM_D2R)); } ImGui::SetCursorPos(prevCurPos); @@ -3141,6 +3168,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPM) { CENTER_TEXT(FM_SHORT_NAME(FM_AM)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_AM)); + TOOLTIP_TEXT(FM_NAME(FM_AM)); bool amOn=op.am; if (ImGui::Checkbox("##AM",&amOn)) { PARAMETER op.am=amOn; @@ -4475,6 +4503,50 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_SNES || ins->type==DIV_INS_NAMCO) { if (ImGui::BeginTabItem("Wavetable")) { + switch (ins->type) { + case DIV_INS_GB: + case DIV_INS_NAMCO: + case DIV_INS_SWAN: + wavePreviewLen=32; + wavePreviewHeight=15; + break; + case DIV_INS_PCE: + wavePreviewLen=32; + wavePreviewHeight=31; + break; + case DIV_INS_VBOY: + wavePreviewLen=32; + wavePreviewHeight=63; + break; + case DIV_INS_SCC: + wavePreviewLen=32; + wavePreviewHeight=255; + break; + case DIV_INS_FDS: + wavePreviewLen=64; + wavePreviewHeight=63; + break; + case DIV_INS_N163: + wavePreviewLen=ins->n163.waveLen; + wavePreviewHeight=15; + break; + case DIV_INS_X1_010: + wavePreviewLen=128; + wavePreviewHeight=255; + break; + case DIV_INS_AMIGA: + wavePreviewLen=ins->amiga.waveLen+1; + wavePreviewHeight=255; + break; + case DIV_INS_SNES: + wavePreviewLen=ins->amiga.waveLen+1; + wavePreviewHeight=15; + break; + default: + wavePreviewLen=32; + wavePreviewHeight=31; + break; + } if (ImGui::Checkbox("Enable synthesizer",&ins->ws.enabled)) { wavePreviewInit=true; } @@ -4512,7 +4584,8 @@ void FurnaceGUI::drawInsEdit() { ImGui::Unindent(); ImGui::EndCombo(); } - if (ImGui::BeginTable("WSPreview",3)) { + const bool isSingleWaveFX=(ins->ws.effect>=128); + if (ImGui::BeginTable("WSPreview",isSingleWaveFX?3:2)) { DivWavetable* wave1=e->getWave(ins->ws.wave1); DivWavetable* wave2=e->getWave(ins->ws.wave2); if (wavePreviewInit) { @@ -4545,15 +4618,19 @@ void FurnaceGUI::drawInsEdit() { } } + float ySize=(isSingleWaveFX?96.0f:128.0f)*dpiScale; + ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImVec2 size1=ImVec2(ImGui::GetContentRegionAvail().x,64.0f*dpiScale); + ImVec2 size1=ImVec2(ImGui::GetContentRegionAvail().x,ySize); PlotNoLerp("##WaveformP1",wavePreview1,wave1->len+1,0,"Wave 1",0,wave1->max,size1); + if (isSingleWaveFX) { + ImGui::TableNextColumn(); + ImVec2 size2=ImVec2(ImGui::GetContentRegionAvail().x,ySize); + PlotNoLerp("##WaveformP2",wavePreview2,wave2->len+1,0,"Wave 2",0,wave2->max,size2); + } ImGui::TableNextColumn(); - ImVec2 size2=ImVec2(ImGui::GetContentRegionAvail().x,64.0f*dpiScale); - PlotNoLerp("##WaveformP2",wavePreview2,wave2->len+1,0,"Wave 2",0,wave2->max,size2); - ImGui::TableNextColumn(); - ImVec2 size3=ImVec2(ImGui::GetContentRegionAvail().x,64.0f*dpiScale); + ImVec2 size3=ImVec2(ImGui::GetContentRegionAvail().x,ySize); PlotNoLerp("##WaveformP3",wavePreview3,wavePreviewLen,0,"Result",0,wavePreviewHeight,size3); ImGui::TableNextRow(); @@ -4566,28 +4643,23 @@ void FurnaceGUI::drawInsEdit() { if (ins->ws.wave1>=(int)e->song.wave.size()) ins->ws.wave1=e->song.wave.size()-1; wavePreviewInit=true; } - ImGui::TableNextColumn(); - ImGui::Text("Wave 2"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##SelWave2",&ins->ws.wave2,1,4)) { - if (ins->ws.wave2<0) ins->ws.wave2=0; - if (ins->ws.wave2>=(int)e->song.wave.size()) ins->ws.wave2=e->song.wave.size()-1; - wavePreviewInit=true; + if (isSingleWaveFX) { + ImGui::TableNextColumn(); + ImGui::Text("Wave 2"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SelWave2",&ins->ws.wave2,1,4)) { + if (ins->ws.wave2<0) ins->ws.wave2=0; + if (ins->ws.wave2>=(int)e->song.wave.size()) ins->ws.wave2=e->song.wave.size()-1; + wavePreviewInit=true; + } } ImGui::TableNextColumn(); if (ImGui::Button(ICON_FA_REPEAT "##WSRestart")) { wavePreviewInit=true; } ImGui::SameLine(); - ImGui::Text("Preview Width"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##SelWave3",&wavePreviewLen,1,4)) { - if (wavePreviewLen<1) wavePreviewLen=1; - if (wavePreviewLen>256) wavePreviewLen=256; - wavePreviewInit=true; - } + ImGui::Text("(%d×%d)",wavePreviewLen,wavePreviewHeight+1); ImGui::EndTable(); } diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 826df20f..5f822078 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -29,6 +29,14 @@ void FurnaceGUI::drawOrders() { nextWindow=GUI_WINDOW_NOTHING; } if (!ordersOpen) return; + if (mobileUI) { + patWindowPos=(portrait?ImVec2(0.0f,(mobileMenuPos*-0.65*canvasH)):ImVec2((0.16*canvasH)+0.5*canvasW*mobileMenuPos,0.0f)); + patWindowSize=(portrait?ImVec2(canvasW,canvasH-(0.16*canvasW)):ImVec2(canvasW-(0.16*canvasH),canvasH)); + ImGui::SetNextWindowPos(patWindowPos); + ImGui::SetNextWindowSize(patWindowSize); + } else { + //ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(canvasW,canvasH)); + } if (ImGui::Begin("Orders",&ordersOpen,globalWinFlags)) { float regionX=ImGui::GetContentRegionAvail().x; ImVec2 prevSpacing=ImGui::GetStyle().ItemSpacing; @@ -94,7 +102,7 @@ void FurnaceGUI::drawOrders() { //} ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->curOrders->ord[j][i]==e->curOrders->ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]); - if (ImGui::Selectable(selID,(orderEditMode!=0 && curOrder==i && orderCursor==j))) { + if (ImGui::Selectable(selID,settings.ordersCursor?(cursor.xCoarse==j && oldOrder1!=i):false)) { if (curOrder==i) { if (orderEditMode==0) { prepareUndo(GUI_UNDO_CHANGE_ORDER); @@ -127,6 +135,11 @@ void FurnaceGUI::drawOrders() { } } ImGui::PopStyleColor(); + if (orderEditMode!=0 && curOrder==i && orderCursor==j) { + // draw a border + ImDrawList* dl=ImGui::GetWindowDrawList(); + dl->AddRect(ImGui::GetItemRectMin(),ImGui::GetItemRectMax(),ImGui::GetColorU32(uiColors[GUI_COLOR_TEXT]),2.0f*dpiScale); + } if (!pat->name.empty() && ImGui::IsItemHovered()) { ImGui::SetTooltip("%s",pat->name.c_str()); } diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 00e9b510..dbfee302 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -437,7 +437,7 @@ void FurnaceGUI::drawPattern() { if (ImGui::Selectable((extraChannelButtons==2)?" --##ExtraChannelButtons":" ++##ExtraChannelButtons",false,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale))) { if (++extraChannelButtons>2) extraChannelButtons=0; } - if (ImGui::IsItemHovered()) { + if (ImGui::IsItemHovered() && !mobileUI) { if (extraChannelButtons==2) { ImGui::SetTooltip("Pattern names (click to collapse)\nRight-click for visualizer"); } else if (extraChannelButtons==1) { @@ -706,7 +706,7 @@ void FurnaceGUI::drawPattern() { if (extraChannelButtons==0 || settings.channelVolStyle!=0) ImGui::PopStyleVar(); - if (displayTooltip && ImGui::IsItemHovered()) { + if (displayTooltip && ImGui::IsItemHovered() && !mobileUI) { ImGui::SetTooltip("%s",e->getChannelName(i)); } if (settings.channelFont==0) ImGui::PopFont(); diff --git a/src/gui/piano.cpp b/src/gui/piano.cpp index cb9ce969..1e606d77 100644 --- a/src/gui/piano.cpp +++ b/src/gui/piano.cpp @@ -405,7 +405,7 @@ void FurnaceGUI::drawPiano() { break; case GUI_WINDOW_SAMPLE_LIST: case GUI_WINDOW_SAMPLE_EDIT: - e->previewSample(curWave,note); + e->previewSample(curSample,note); break; default: e->synchronized([this,note]() { diff --git a/src/gui/scaling.cpp b/src/gui/scaling.cpp index c1fd5bcc..aee55c8c 100644 --- a/src/gui/scaling.cpp +++ b/src/gui/scaling.cpp @@ -196,11 +196,20 @@ double getScaleFactor(const char* driverHint) { #endif // SDL fallback +#ifdef ANDROID + float dpiScaleF=192.0f; + if (SDL_GetDisplayDPI(0,&dpiScaleF,NULL,NULL)==0) { + ret=round(dpiScaleF/192.0f); + if (ret<1) ret=1; + } + +#else float dpiScaleF=96.0f; if (SDL_GetDisplayDPI(0,&dpiScaleF,NULL,NULL)==0) { ret=round(dpiScaleF/96.0f); if (ret<1) ret=1; } +#endif // couldn't detect scaling factor :< return ret; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 9db0e04b..6cf332fc 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -40,7 +40,7 @@ #define POWER_SAVE_DEFAULT 0 #endif -#ifdef __HAIKU__ +#if defined(__HAIKU__) // NFD doesn't support Haiku #define SYS_FILE_DIALOG_DEFAULT 0 #else @@ -548,6 +548,24 @@ void FurnaceGUI::drawSettings() { settings.saveUnusedPatterns=saveUnusedPatternsB; } + ImGui::Text("Audio export loop/fade out time:"); + if (ImGui::RadioButton("Set to these values on start-up:##fot0",settings.persistFadeOut==0)) { + settings.persistFadeOut=0; + } + ImGui::BeginDisabled(settings.persistFadeOut); + if (ImGui::InputInt("Loops",&settings.exportLoops,1,2)) { + if (exportLoops<0) exportLoops=0; + exportLoops=settings.exportLoops; + } + if (ImGui::InputDouble("Fade out (seconds)",&settings.exportFadeOut,1.0,2.0,"%.1f")) { + if (exportFadeOut<0.0) exportFadeOut=0.0; + exportFadeOut=settings.exportFadeOut; + } + ImGui::EndDisabled(); + if (ImGui::RadioButton("Remember last values##fot1",settings.persistFadeOut==1)) { + settings.persistFadeOut=1; + } + ImGui::Text("Note preview behavior:"); if (ImGui::RadioButton("Never##npb0",settings.notePreviewBehavior==0)) { settings.notePreviewBehavior=0; @@ -1459,6 +1477,11 @@ void FurnaceGUI::drawSettings() { settings.sysSeparators=sysSeparatorsB; }*/ + bool ordersCursorB=settings.ordersCursor; + if (ImGui::Checkbox("Highlight channel at cursor in Orders",&ordersCursorB)) { + settings.ordersCursor=ordersCursorB; + } + bool partyTimeB=settings.partyTime; if (ImGui::Checkbox("About screen party time",&partyTimeB)) { settings.partyTime=partyTimeB; @@ -2280,7 +2303,7 @@ void FurnaceGUI::syncSettings() { settings.wrapVertical=e->getConfInt("wrapVertical",0); settings.macroView=e->getConfInt("macroView",0); settings.fmNames=e->getConfInt("fmNames",0); - settings.allowEditDocking=e->getConfInt("allowEditDocking",0); + settings.allowEditDocking=e->getConfInt("allowEditDocking",1); settings.chipNames=e->getConfInt("chipNames",0); settings.overflowHighlight=e->getConfInt("overflowHighlight",0); settings.partyTime=e->getConfInt("partyTime",0); @@ -2370,6 +2393,10 @@ void FurnaceGUI::syncSettings() { settings.midiOutClock=e->getConfInt("midiOutClock",0); settings.midiOutMode=e->getConfInt("midiOutMode",1); settings.centerPattern=e->getConfInt("centerPattern",0); + settings.ordersCursor=e->getConfInt("ordersCursor",1); + settings.persistFadeOut=e->getConfInt("persistFadeOut",1); + settings.exportLoops=e->getConfInt("exportLoops",0); + settings.exportFadeOut=e->getConfDouble("exportFadeOut",0.0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2474,6 +2501,11 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.midiOutClock,0,1); clampSetting(settings.midiOutMode,0,2); clampSetting(settings.centerPattern,0,1); + clampSetting(settings.ordersCursor,0,1); + clampSetting(settings.persistFadeOut,0,1); + + if (settings.exportLoops<0.0) settings.exportLoops=0.0; + if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0; String initialSys2=e->getConfString("initialSys2",""); if (initialSys2.empty()) { @@ -2639,6 +2671,10 @@ void FurnaceGUI::commitSettings() { e->setConf("midiOutClock",settings.midiOutClock); e->setConf("midiOutMode",settings.midiOutMode); e->setConf("centerPattern",settings.centerPattern); + e->setConf("ordersCursor",settings.ordersCursor); + e->setConf("persistFadeOut",settings.persistFadeOut); + e->setConf("exportLoops",settings.exportLoops); + e->setConf("exportFadeOut",settings.exportFadeOut); // colors for (int i=0; i bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool modifyOnChange) { @@ -1289,6 +1290,51 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo } rightClickable } + if (ImGui::Button(snesFilterHex?"Hex##SNESFHex":"Dec##SNESFHex")) { + snesFilterHex=!snesFilterHex; + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); // wavetable text input size found here + if (ImGui::InputText("##MMLWave",&mmlStringSNES)) { + int actualData[256]; + int discardIt=0; + memset(actualData,0,256*sizeof(int)); + decodeMMLStrW(mmlStringSNES,actualData,discardIt,snesFilterHex?0:-128,snesFilterHex?255:127,snesFilterHex); + if (snesFilterHex) { + for (int i=0; i<8; i++) { + if (actualData[i]>=128) actualData[i]-=256; + } + } + memcpy(echoFilter,actualData,8*sizeof(int)); + altered=true; + } + if (!ImGui::IsItemActive()) { + int actualData[8]; + for (int i=0; i<8; i++) { + if (echoFilter[i]<0 && snesFilterHex) { + actualData[i]=echoFilter[i]+256; + } else { + actualData[i]=echoFilter[i]; + } + } + encodeMMLStr(mmlStringSNES,actualData,8,-1,-1,snesFilterHex); + } + + int filterSum=( + echoFilter[0]+ + echoFilter[1]+ + echoFilter[2]+ + echoFilter[3]+ + echoFilter[4]+ + echoFilter[5]+ + echoFilter[6]+ + echoFilter[7] + ); + + ImGui::PushStyleColor(ImGuiCol_Text,(filterSum<-128 || filterSum>127)?uiColors[GUI_COLOR_LOGLEVEL_WARNING]:uiColors[GUI_COLOR_TEXT]); + ImGui::Text("sum: %d",filterSum); + ImGui::PopStyleColor(); + if (altered) { e->lockSave([&]() { flags.set("volScaleL",127-vsL); diff --git a/src/gui/util.cpp b/src/gui/util.cpp index 2bbf2876..3998f2bb 100644 --- a/src/gui/util.cpp +++ b/src/gui/util.cpp @@ -32,6 +32,15 @@ #endif String getHomeDir() { +#ifdef IS_MOBILE + +#ifdef ANDROID + return "/storage/emulated/0/"; +#else + return "/"; +#endif + +#else String ret; char tempDir[4096]; @@ -73,6 +82,7 @@ String getHomeDir() { } return ret; +#endif } String getKeyName(int key, bool emptyNone) { @@ -101,4 +111,4 @@ String getKeyName(int key, bool emptyNone) { ret+=name; } return ret; -} \ No newline at end of file +} diff --git a/src/main.cpp b/src/main.cpp index 8c3890f1..5c668a9e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -317,6 +317,13 @@ void reportError(String what) { logE("%s",what); MessageBox(NULL,what.c_str(),"Furnace",MB_OK|MB_ICONERROR); } +#elif defined(ANDROID) +void reportError(String what) { + logE("%s",what); +#ifdef HAVE_SDL2 + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,"Error",what.c_str(),NULL); +#endif +} #else void reportError(String what) { logE("%s",what);