furnace/src/main.cpp

468 lines
12 KiB
C++
Raw Normal View History

2022-02-15 03:12:20 +00:00
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 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.
*/
#include <stdio.h>
2022-01-18 02:08:14 +00:00
#include <stdint.h>
#include <string>
2022-01-27 09:25:16 +00:00
#ifdef HAVE_GUI
#include "SDL_events.h"
2022-01-27 09:25:16 +00:00
#endif
#include "ta-log.h"
#include "fileutils.h"
#include "engine/engine.h"
2021-05-28 20:25:55 +00:00
#ifdef _WIN32
2021-05-28 20:57:07 +00:00
#define WIN32_LEAN_AND_MEAN
2021-05-28 20:25:55 +00:00
#include <windows.h>
2021-12-16 20:51:19 +00:00
#include <shellapi.h>
2021-05-28 20:25:55 +00:00
#else
#include <unistd.h>
#endif
2021-12-11 08:11:40 +00:00
#ifdef HAVE_GUI
#include "gui/gui.h"
#endif
2022-05-29 04:40:46 +00:00
#ifdef HAVE_BACKWARD
#include "../extern/backward/backward.hpp"
#endif
DivEngine e;
2021-12-11 08:11:40 +00:00
#ifdef HAVE_GUI
FurnaceGUI g;
#endif
2021-12-07 17:21:23 +00:00
String outName;
String vgmOutName;
2022-01-23 04:50:49 +00:00
int loops=1;
DivAudioExportModes outMode=DIV_EXPORT_MODE_ONE;
2021-12-07 17:21:23 +00:00
2021-12-11 08:11:40 +00:00
#ifdef HAVE_GUI
bool consoleMode=false;
#else
bool consoleMode=true;
#endif
2022-02-06 04:48:56 +00:00
bool displayEngineFailError=false;
2021-06-09 08:33:03 +00:00
std::vector<TAParam> params;
TAParamResult pHelp(String) {
2021-06-09 08:33:03 +00:00
printf("usage: furnace [params] [filename]\n"
"you may specify the following parameters:\n");
for (auto& i: params) {
if (i.value) {
printf(" -%s %s: %s\n",i.name.c_str(),i.valName.c_str(),i.desc.c_str());
} else {
printf(" -%s: %s\n",i.name.c_str(),i.desc.c_str());
}
}
return TA_PARAM_QUIT;
2021-06-09 08:33:03 +00:00
}
TAParamResult pAudio(String val) {
2022-01-23 04:50:49 +00:00
if (outName!="") {
logE("can't use -audio and -output at the same time.");
return TA_PARAM_ERROR;
2022-01-23 04:50:49 +00:00
}
2021-06-09 08:33:03 +00:00
if (val=="jack") {
e.setAudio(DIV_AUDIO_JACK);
} else if (val=="sdl") {
e.setAudio(DIV_AUDIO_SDL);
} else {
logE("invalid value for audio engine! valid values are: jack, sdl.");
return TA_PARAM_ERROR;
2021-06-09 08:33:03 +00:00
}
return TA_PARAM_SUCCESS;
2021-06-09 08:33:03 +00:00
}
TAParamResult pView(String val) {
2021-06-09 08:33:03 +00:00
if (val=="pattern") {
e.setView(DIV_STATUS_PATTERN);
} else if (val=="commands") {
e.setView(DIV_STATUS_COMMANDS);
} else if (val=="nothing") {
e.setView(DIV_STATUS_NOTHING);
} else {
logE("invalid value for view type! valid values are: pattern, commands, nothing.");
return TA_PARAM_ERROR;
2021-06-09 08:33:03 +00:00
}
return TA_PARAM_SUCCESS;
2021-06-09 08:33:03 +00:00
}
TAParamResult pConsole(String val) {
2021-12-11 08:11:40 +00:00
consoleMode=true;
return TA_PARAM_SUCCESS;
2021-12-11 08:11:40 +00:00
}
TAParamResult pLogLevel(String val) {
2022-03-24 03:05:09 +00:00
if (val=="trace") {
logLevel=LOGLEVEL_TRACE;
} else if (val=="debug") {
2021-06-09 17:28:46 +00:00
logLevel=LOGLEVEL_DEBUG;
} else if (val=="info") {
logLevel=LOGLEVEL_INFO;
} else if (val=="warning") {
logLevel=LOGLEVEL_WARN;
} else if (val=="error") {
logLevel=LOGLEVEL_ERROR;
} else {
logE("invalid value for loglevel! valid values are: trace, debug, info, warning, error.");
return TA_PARAM_ERROR;
2021-06-09 17:28:46 +00:00
}
return TA_PARAM_SUCCESS;
2021-06-09 17:28:46 +00:00
}
TAParamResult pVersion(String) {
printf("Furnace version " DIV_VERSION ".\n\n");
2022-02-15 02:59:26 +00:00
printf("copyright (C) 2021-2022 tildearrow and contributors.\n");
printf("licensed under the GNU General Public License version 2 or later\n");
2021-06-09 08:33:03 +00:00
printf("<https://www.gnu.org/licenses/old-licenses/gpl-2.0.html>.\n\n");
printf("this is free software with ABSOLUTELY NO WARRANTY.\n");
printf("pass the -warranty parameter for more information.\n\n");
printf("DISCLAIMER: this program is not affiliated with Delek in any form.\n");
2021-12-07 18:01:59 +00:00
printf("\n");
printf("furnace is powered by:\n");
2021-12-07 18:01:59 +00:00
printf("- libsndfile by Erik de Castro Lopo and rest of libsndfile team (LGPLv2.1)\n");
printf("- SDL2 by Sam Lantinga (zlib license)\n");
printf("- zlib by Jean-loup Gailly and Mark Adler (zlib license)\n");
2021-12-11 08:11:40 +00:00
printf("- Dear ImGui by Omar Cornut (MIT)\n");
2021-12-07 18:01:59 +00:00
printf("- Nuked-OPM by Nuke.YKT (LGPLv2.1)\n");
printf("- Nuked-OPN2 by Nuke.YKT (LGPLv2.1)\n");
2021-12-11 08:11:40 +00:00
printf("- ymfm by Aaron Giles (BSD 3-clause)\n");
2021-12-07 18:01:59 +00:00
printf("- MAME SN76496 emulation core by Nicola Salmoria (BSD 3-clause)\n");
printf("- MAME AY-3-8910 emulation core by Couriersud (BSD 3-clause)\n");
2022-01-16 06:47:19 +00:00
printf("- MAME SAA1099 emulation core by Juergen Buchmueller and Manuel Abadia (BSD 3-clause)\n");
2022-02-13 23:04:23 +00:00
printf("- SAASound (BSD 3-clause)\n");
2021-12-07 18:01:59 +00:00
printf("- SameBoy by Lior Halphon (MIT)\n");
printf("- Mednafen PCE by Mednafen Team (GPLv2)\n");
printf("- puNES by FHorse (GPLv2)\n");
printf("- reSID by Dag Lem (GPLv2)\n");
printf("- Stella by Stella Team (GPLv2)\n");
return TA_PARAM_QUIT;
2021-06-09 08:33:03 +00:00
}
TAParamResult pWarranty(String) {
2021-06-09 08:33:03 +00:00
printf("This program is free software; you can redistribute it and/or\n"
"modify it under the terms of the GNU General Public License\n"
"as published by the Free Software Foundation; either version 2\n"
"of the License, or (at your option) any later version.\n\n"
"This program is distributed in the hope that it will be useful,\n"
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
"GNU General Public License for more details.\n\n"
"You should have received a copy of the GNU General Public License\n"
"along with this program; if not, write to the Free Software\n"
"Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n");
return TA_PARAM_QUIT;
2021-06-09 08:33:03 +00:00
}
TAParamResult pLoops(String val) {
try {
int count=std::stoi(val);
if (count<0) {
2022-01-23 04:50:49 +00:00
loops=0;
} else {
2022-01-23 04:50:49 +00:00
loops=count+1;
}
} catch (std::exception& e) {
logE("loop count shall be a number.");
return TA_PARAM_ERROR;
}
return TA_PARAM_SUCCESS;
}
TAParamResult pOutMode(String val) {
2022-01-23 04:50:49 +00:00
if (val=="one") {
outMode=DIV_EXPORT_MODE_ONE;
} else if (val=="persys") {
outMode=DIV_EXPORT_MODE_MANY_SYS;
} else if (val=="perchan") {
outMode=DIV_EXPORT_MODE_MANY_CHAN;
} else {
logE("invalid value for outmode! valid values are: one, persys and perchan.");
return TA_PARAM_ERROR;
2022-01-23 04:50:49 +00:00
}
return TA_PARAM_SUCCESS;
2022-01-23 04:50:49 +00:00
}
TAParamResult pOutput(String val) {
2021-12-07 17:21:23 +00:00
outName=val;
2022-01-23 04:50:49 +00:00
e.setAudio(DIV_AUDIO_DUMMY);
return TA_PARAM_SUCCESS;
}
TAParamResult pVGMOut(String val) {
vgmOutName=val;
e.setAudio(DIV_AUDIO_DUMMY);
return TA_PARAM_SUCCESS;
}
2021-06-09 08:33:03 +00:00
bool needsValue(String param) {
for (size_t i=0; i<params.size(); i++) {
if (params[i].name==param) {
return params[i].value;
}
}
return false;
}
void initParams() {
params.push_back(TAParam("h","help",false,pHelp,"","display this help"));
params.push_back(TAParam("a","audio",true,pAudio,"jack|sdl","set audio engine (SDL by default)"));
params.push_back(TAParam("o","output",true,pOutput,"<filename>","output audio to file"));
params.push_back(TAParam("O","vgmout",true,pVGMOut,"<filename>","output .vgm data"));
2021-06-09 17:28:46 +00:00
params.push_back(TAParam("L","loglevel",true,pLogLevel,"debug|info|warning|error","set the log level (info by default)"));
2021-06-09 08:33:03 +00:00
params.push_back(TAParam("v","view",true,pView,"pattern|commands|nothing","set visualization (pattern by default)"));
2021-12-11 08:11:40 +00:00
params.push_back(TAParam("c","console",false,pConsole,"","enable console mode"));
2021-06-09 08:33:03 +00:00
params.push_back(TAParam("l","loops",true,pLoops,"<count>","set number of loops (-1 means loop forever)"));
2022-01-23 04:50:49 +00:00
params.push_back(TAParam("o","outmode",true,pOutMode,"one|persys|perchan","set file output mode"));
params.push_back(TAParam("V","version",false,pVersion,"","view information about Furnace."));
2021-06-09 08:33:03 +00:00
params.push_back(TAParam("W","warranty",false,pWarranty,"","view warranty disclaimer."));
}
2022-04-15 21:00:21 +00:00
// TODO: CoInitializeEx on Windows?
// TODO: add crash log
int main(int argc, char** argv) {
2022-05-29 04:40:46 +00:00
backward::SignalHandling sh;
initLog();
2022-05-08 20:59:42 +00:00
#if !(defined(__APPLE__) || defined(_WIN32) || defined(ANDROID))
// workaround for Wayland HiDPI issue
if (getenv("SDL_VIDEODRIVER")==NULL) {
setenv("SDL_VIDEODRIVER","x11",1);
}
#endif
2021-12-07 17:21:23 +00:00
outName="";
vgmOutName="";
2021-06-09 08:33:03 +00:00
initParams();
// parse arguments
String arg, val, fileName;
size_t eqSplit, argStart;
for (int i=1; i<argc; i++) {
arg=""; val="";
if (argv[i][0]=='-') {
if (argv[i][1]=='-') {
argStart=2;
} else {
argStart=1;
}
arg=&argv[i][argStart];
eqSplit=arg.find_first_of('=');
if (eqSplit==String::npos) {
if (needsValue(arg)) {
if ((i+1)<argc) {
val=argv[i+1];
i++;
} else {
logE("incomplete param %s.",arg.c_str());
2021-06-09 08:33:03 +00:00
return 1;
}
}
} else {
val=arg.substr(eqSplit+1);
arg=arg.substr(0,eqSplit);
}
for (size_t j=0; j<params.size(); j++) {
if (params[j].name==arg || params[j].shortName==arg) {
switch (params[j].func(val)) {
case TA_PARAM_ERROR:
return 1;
break;
case TA_PARAM_SUCCESS:
break;
case TA_PARAM_QUIT:
return 0;
break;
}
2021-06-09 08:33:03 +00:00
break;
}
}
} else {
fileName=argv[i];
}
}
2022-05-29 04:40:46 +00:00
#ifdef _WIN32
2022-05-29 05:04:33 +00:00
if (!sh.loaded()) {
MessageBox(NULL,"crash backtrace not available!","Warning",MB_OK|MB_ICONWARNING);
} else {
MessageBox(NULL,"it will work","Notice",MB_OK|MB_ICONINFORMATION);
2022-05-29 04:40:46 +00:00
}
2022-05-29 05:04:33 +00:00
#endif
2022-05-29 04:40:46 +00:00
e.setConsoleMode(consoleMode);
#ifdef _WIN32
if (consoleMode) {
HANDLE winin=GetStdHandle(STD_INPUT_HANDLE);
HANDLE winout=GetStdHandle(STD_OUTPUT_HANDLE);
int termprop=0;
int termpropi=0;
GetConsoleMode(winout,(LPDWORD)&termprop);
GetConsoleMode(winin,(LPDWORD)&termpropi);
termprop|=ENABLE_VIRTUAL_TERMINAL_PROCESSING;
termpropi&=~ENABLE_LINE_INPUT;
SetConsoleMode(winout,termprop);
SetConsoleMode(winin,termpropi);
}
#endif
if (fileName.empty() && consoleMode) {
logI("usage: %s file",argv[0]);
return 1;
}
logI("Furnace version " DIV_VERSION ".");
if (!fileName.empty()) {
logI("loading module...");
FILE* f=ps_fopen(fileName.c_str(),"rb");
if (f==NULL) {
perror("error");
return 1;
}
if (fseek(f,0,SEEK_END)<0) {
perror("size error");
fclose(f);
return 1;
}
ssize_t len=ftell(f);
2022-01-18 02:08:14 +00:00
if (len==(SIZE_MAX>>1)) {
perror("could not get file length");
fclose(f);
return 1;
}
if (len<1) {
if (len==0) {
printf("that file is empty!\n");
} else {
perror("tell error");
}
fclose(f);
return 1;
}
unsigned char* file=new unsigned char[len];
if (fseek(f,0,SEEK_SET)<0) {
perror("size error");
fclose(f);
2021-12-16 07:21:43 +00:00
delete[] file;
return 1;
}
if (fread(file,1,(size_t)len,f)!=(size_t)len) {
perror("read error");
fclose(f);
2021-12-16 07:21:43 +00:00
delete[] file;
return 1;
}
fclose(f);
2021-12-16 07:21:43 +00:00
if (!e.load(file,(size_t)len)) {
logE("could not open file!");
return 1;
}
}
if (!e.init()) {
logE("could not initialize engine!");
2022-02-06 04:48:56 +00:00
if (consoleMode) {
return 1;
} else {
2022-02-06 21:21:48 +00:00
displayEngineFailError=true;
2022-02-06 04:48:56 +00:00
}
}
if (outName!="" || vgmOutName!="") {
if (vgmOutName!="") {
SafeWriter* w=e.saveVGM();
if (w!=NULL) {
FILE* f=fopen(vgmOutName.c_str(),"wb");
if (f!=NULL) {
fwrite(w->getFinalBuf(),1,w->size(),f);
fclose(f);
} else {
logE("could not open file! %s",strerror(errno));
}
w->finish();
delete w;
} else {
logE("could not write VGM!");
}
}
if (outName!="") {
e.setConsoleMode(true);
e.saveAudio(outName.c_str(),loops,outMode);
e.waitAudioFile();
}
2022-01-23 04:50:49 +00:00
return 0;
}
2021-12-11 08:11:40 +00:00
if (consoleMode) {
logI("playing...");
2021-12-11 08:11:40 +00:00
e.play();
2022-01-27 09:25:16 +00:00
#ifdef HAVE_GUI
SDL_Event ev;
2021-12-11 08:11:40 +00:00
while (true) {
SDL_WaitEvent(&ev);
if (ev.type==SDL_QUIT) break;
2021-12-11 08:11:40 +00:00
}
e.quit();
2021-12-11 08:11:40 +00:00
return 0;
2022-01-27 09:25:16 +00:00
#else
2022-01-27 09:27:21 +00:00
while (true) {
2022-01-27 09:25:16 +00:00
#ifdef _WIN32
2022-01-27 09:27:21 +00:00
Sleep(500);
2022-01-27 09:25:16 +00:00
#else
2022-01-27 09:27:21 +00:00
usleep(500000);
2022-01-27 09:25:16 +00:00
#endif
2022-01-27 09:27:21 +00:00
}
2022-01-27 09:25:16 +00:00
#endif
}
2021-12-11 08:11:40 +00:00
2021-12-11 18:34:11 +00:00
#ifdef HAVE_GUI
2021-12-11 08:11:40 +00:00
g.bindEngine(&e);
if (!g.init()) return 1;
2021-12-11 08:11:40 +00:00
2022-02-06 04:48:56 +00:00
if (displayEngineFailError) {
logE("displaying engine fail error.");
2022-02-06 04:48:56 +00:00
g.showError("error while initializing audio!");
}
if (!fileName.empty()) {
g.setFileName(fileName);
}
2021-12-11 08:11:40 +00:00
g.loop();
logI("closing GUI.");
2021-12-19 21:01:24 +00:00
g.finish();
2021-12-11 18:34:11 +00:00
#else
logE("GUI requested but GUI not compiled!");
2021-12-11 18:34:11 +00:00
#endif
2021-12-13 22:09:46 +00:00
logI("stopping engine.");
2021-12-13 22:09:46 +00:00
e.quit();
return 0;
}
2021-12-16 20:51:19 +00:00
#ifdef _WIN32
#include "winMain.cpp"
#endif