diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 2a000476d..9b532c0bf 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "dispatch.h" #define _USE_MATH_DEFINES #include "engine.h" #include "instrument.h" @@ -234,6 +235,111 @@ double DivEngine::benchmarkSeek() { return tAvg; } +SafeWriter* DivEngine::saveCommand(bool binary) { + logI("implement! %d",binary); + stop(); + repeatPattern=false; + setOrder(0); + BUSY_BEGIN_SOFT; + // determine loop point + int loopOrder=0; + int loopRow=0; + int loopEnd=0; + walkSong(loopOrder,loopRow,loopEnd); + logI("loop point: %d %d",loopOrder,loopRow); + + SafeWriter* w=new SafeWriter; + w->init(); + + // write header + w->writeText("# Furnace Command Stream\n\n"); + + w->writeText("[Information]\n"); + w->writeText(fmt::sprintf("name: %s\n",song.name)); + w->writeText(fmt::sprintf("author: %s\n",song.author)); + w->writeText(fmt::sprintf("category: %s\n",song.category)); + w->writeText(fmt::sprintf("system: %s\n",song.systemName)); + + w->writeText("\n"); + + w->writeText("[SubSongInformation]\n"); + w->writeText(fmt::sprintf("name: %s\n",curSubSong->name)); + w->writeText(fmt::sprintf("tickRate: %f\n",curSubSong->hz)); + + w->writeText("\n"); + + w->writeText("[SysDefinition]\n"); + // TODO + + w->writeText("\n"); + + // play the song ourselves + bool done=false; + playSub(false); + + w->writeText("[Stream]\n"); + int tick=0; + bool oldCmdStreamEnabled=cmdStreamEnabled; + cmdStreamEnabled=true; + double curDivider=divider; + while (!done) { + if (nextTick(false,true) || !playing) { + done=true; + } + // get command stream + bool wroteTick=false; + if (curDivider!=divider) { + curDivider=divider; + if (!wroteTick) { + wroteTick=true; + w->writeText(fmt::sprintf(">> TICK %d\n",tick)); + } + w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider)); + } + for (DivCommand& i: cmdStream) { + switch (i.cmd) { + // strip away hinted/useless commands + case DIV_ALWAYS_SET_VOLUME: + break; + case DIV_CMD_GET_VOLUME: + break; + case DIV_CMD_VOLUME: + break; + case DIV_CMD_NOTE_PORTA: + break; + case DIV_CMD_LEGATO: + break; + case DIV_CMD_PITCH: + break; + default: + if (!wroteTick) { + wroteTick=true; + w->writeText(fmt::sprintf(">> TICK %d\n",tick)); + } + w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2)); + break; + } + } + cmdStream.clear(); + tick++; + } + cmdStreamEnabled=oldCmdStreamEnabled; + + if (!playing) { + w->writeText(">> END\n"); + } else { + w->writeText(">> LOOP 0\n"); + } + + remainingLoops=-1; + playing=false; + freelance=false; + extValuePresent=false; + BUSY_END; + + return w; +} + void _runExportThread(DivEngine* caller) { caller->runExportThread(); } diff --git a/src/engine/engine.h b/src/engine/engine.h index 8fd56ff91..6f969efc9 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -280,6 +280,8 @@ enum DivChanTypes { DIV_CH_OP=5 }; +extern const char* cmdName[]; + class DivEngine { DivDispatchContainer disCont[32]; TAAudio* output; diff --git a/src/engine/safeWriter.cpp b/src/engine/safeWriter.cpp index f29800a4a..e61380936 100644 --- a/src/engine/safeWriter.cpp +++ b/src/engine/safeWriter.cpp @@ -120,6 +120,9 @@ int SafeWriter::writeWString(WString val, bool pascal) { return 2+val.size()*2; } } +int SafeWriter::writeText(String val) { + return write(val.c_str(),val.size()); +} void SafeWriter::init() { if (operative) return; diff --git a/src/engine/safeWriter.h b/src/engine/safeWriter.h index 9072c61d7..414417fd2 100644 --- a/src/engine/safeWriter.h +++ b/src/engine/safeWriter.h @@ -57,6 +57,7 @@ class SafeWriter { int writeD_BE(double val); int writeWString(WString val, bool pascal); int writeString(String val, bool pascal); + int writeText(String val); void init(); SafeReader* toReader(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index c296ab2c3..28edd9e32 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1401,6 +1401,17 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_EXPORT_CMDSTREAM: + if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir(); + hasOpened=fileDialog->openSave( + "Export Command Stream", + {"text file", "*.txt", + "binary file", "*.bin"}, + "text file{.txt},binary file{.bin}", + workingDirROMExport, + dpiScale + ); + break; case GUI_FILE_EXPORT_ROM: showError("Coming soon!"); break; @@ -2947,6 +2958,19 @@ bool FurnaceGUI::loop() { } ImGui::EndMenu(); } + if (ImGui::BeginMenu("export command stream...")) { + ImGui::Text( + "this option exports a text or binary file which\n" + "contains a dump of the internal command stream\n" + "produced when playing the song.\n\n" + + "technical/development use only!" + ); + if (ImGui::Button("export")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); + } + ImGui::EndMenu(); + } ImGui::Separator(); if (ImGui::BeginMenu("add system...")) { for (int j=0; availableSystems[j]; j++) { @@ -3258,9 +3282,12 @@ bool FurnaceGUI::loop() { workingDirAudioExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_EXPORT_VGM: - case GUI_FILE_EXPORT_ROM: workingDirVGMExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; + case GUI_FILE_EXPORT_ROM: + case GUI_FILE_EXPORT_CMDSTREAM: + workingDirROMExport=fileDialog->getPath()+DIR_SEPARATOR_STR; + break; case GUI_FILE_LOAD_MAIN_FONT: case GUI_FILE_LOAD_PAT_FONT: workingDirFont=fileDialog->getPath()+DIR_SEPARATOR_STR; @@ -3325,6 +3352,11 @@ bool FurnaceGUI::loop() { if (curFileDialog==GUI_FILE_EXPORT_VGM) { checkExtension(".vgm"); } + if (curFileDialog==GUI_FILE_EXPORT_CMDSTREAM) { + // we can't tell whether the user chose .txt or .bin in the system file picker + const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="text file")?".txt":".bin"; + checkExtensionDual(".txt",".bin",fallbackExt); + } if (curFileDialog==GUI_FILE_EXPORT_COLORS) { checkExtension(".cfgc"); } @@ -3506,6 +3538,35 @@ bool FurnaceGUI::loop() { case GUI_FILE_EXPORT_ROM: showError("Coming soon!"); break; + case GUI_FILE_EXPORT_CMDSTREAM: { + String lowerCase=fileName; + for (char& i: lowerCase) { + if (i>='A' && i<='Z') i+='a'-'A'; + } + bool isBinary=true; + if ((lowerCase.size()<4 || lowerCase.rfind(".txt")!=lowerCase.size()-4)) { + isBinary=false; + } + + SafeWriter* w=e->saveCommand(isBinary); + if (w!=NULL) { + FILE* f=ps_fopen(copyOfName.c_str(),"wb"); + if (f!=NULL) { + fwrite(w->getFinalBuf(),1,w->size(),f); + fclose(f); + } else { + showError("could not open file!"); + } + w->finish(); + delete w; + if (!e->getWarnings().empty()) { + showWarning(e->getWarnings(),GUI_WARN_GENERIC); + } + } else { + showError(fmt::sprintf("could not write command stream! (%s)",e->getLastError())); + } + break; + } case GUI_FILE_LOAD_MAIN_FONT: settings.mainFontPath=copyOfName; break; @@ -4099,6 +4160,7 @@ bool FurnaceGUI::init() { workingDirSample=e->getConfString("lastDirSample",workingDir); workingDirAudioExport=e->getConfString("lastDirAudioExport",workingDir); workingDirVGMExport=e->getConfString("lastDirVGMExport",workingDir); + workingDirROMExport=e->getConfString("lastDirROMExport",workingDir); workingDirFont=e->getConfString("lastDirFont",workingDir); workingDirColors=e->getConfString("lastDirColors",workingDir); workingDirKeybinds=e->getConfString("lastDirKeybinds",workingDir); @@ -4339,6 +4401,7 @@ bool FurnaceGUI::finish() { e->setConf("lastDirSample",workingDirSample); e->setConf("lastDirAudioExport",workingDirAudioExport); e->setConf("lastDirVGMExport",workingDirVGMExport); + e->setConf("lastDirROMExport",workingDirROMExport); e->setConf("lastDirFont",workingDirFont); e->setConf("lastDirColors",workingDirColors); e->setConf("lastDirKeybinds",workingDirKeybinds); diff --git a/src/gui/gui.h b/src/gui/gui.h index cb61d48dc..0ce4afaee 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -266,6 +266,7 @@ enum FurnaceGUIFileDialogs { GUI_FILE_EXPORT_AUDIO_PER_SYS, GUI_FILE_EXPORT_AUDIO_PER_CHANNEL, GUI_FILE_EXPORT_VGM, + GUI_FILE_EXPORT_CMDSTREAM, GUI_FILE_EXPORT_ROM, GUI_FILE_LOAD_MAIN_FONT, GUI_FILE_LOAD_PAT_FONT, @@ -948,11 +949,14 @@ class FurnaceGUI { bool updateSampleTex; String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile; - String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout, workingDirROM, workingDirTest; + String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport; + String workingDirVGMExport, workingDirROMExport, workingDirFont, workingDirColors, workingDirKeybinds; + String workingDirLayout, workingDirROM, workingDirTest; String mmlString[32]; String mmlStringW; - bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints, wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; + bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints; + bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly; bool displayPendingIns, pendingInsSingle; bool willExport[32];