Merge branch 'master' of https://github.com/tildearrow/furnace into es5506_alt

This commit is contained in:
cam900 2022-11-07 09:45:54 +09:00
commit 7a803f1e73
27 changed files with 433 additions and 67 deletions

View File

@ -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 }}

View File

@ -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

View File

@ -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
}
}
}

BIN
demos/ComicPartytrack20.fur Normal file

Binary file not shown.

BIN
demos/L’ambreSong.fur Normal file

Binary file not shown.

BIN
demos/MMXStageClear.fur Normal file

Binary file not shown.

View File

@ -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.

View File

@ -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() {

View File

@ -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();

View File

@ -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;

View File

@ -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;
}

View File

@ -104,6 +104,7 @@ const char* aboutLine[]={
"fd",
"GENATARi",
"host12prog",
"Lumigado",
"Lunathir",
"plane",
"TheEssem",

View File

@ -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;

View File

@ -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;

View File

@ -4,6 +4,8 @@
#ifdef USE_NFD
#include <nfd.h>
#elif defined(ANDROID)
#include <SDL.h>
#else
#include "../../extern/pfd-fixed/portable-file-dialogs.h"
#endif
@ -87,6 +89,45 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector<String> 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<String> 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) {

View File

@ -15,6 +15,8 @@
#define NFD_NON_THREADED
#endif
#elif defined(ANDROID)
#include <jni.h>
#else
namespace pfd {
class open_file;
@ -36,6 +38,10 @@ class FurnaceGUIFileDialog {
std::thread* dialogS;
std::atomic<bool> dialogOK;
std::vector<String> 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) {}
};

View File

@ -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; i<settings.maxRecentFile; i++) {
String r=e->getConfString(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),

View File

@ -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<DivSystem> sysSearchResults;
std::vector<FurnaceGUISysDef> 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<FurnaceGUIWindows> curWindowThreadSafe;
float peak[2];
float patChanX[DIV_MAX_CHANS+1];
float patChanSlideY[DIV_MAX_CHANS+1];

View File

@ -1852,6 +1852,11 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& 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; i<opCount; i++) {
DivInstrumentFM::Operator& op=ins->fm.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();
}

View File

@ -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());
}

View File

@ -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();

View File

@ -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]() {

View File

@ -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;

View File

@ -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<GUI_COLOR_MAX; i++) {

View File

@ -18,6 +18,7 @@
*/
#include "gui.h"
#include "misc/cpp/imgui_stdlib.h"
#include <imgui.h>
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);

View File

@ -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;
}
}

View File

@ -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);