diff --git a/CMakeLists.txt b/CMakeLists.txt index decea3fef..581e1c2de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -789,6 +789,7 @@ src/gui/channels.cpp src/gui/chanOsc.cpp src/gui/clock.cpp src/gui/compatFlags.cpp +src/gui/csPlayer.cpp src/gui/cursor.cpp src/gui/dataList.cpp src/gui/debugWindow.cpp diff --git a/src/engine/cmdStream.cpp b/src/engine/cmdStream.cpp index 6036364c5..329f9a798 100644 --- a/src/engine/cmdStream.cpp +++ b/src/engine/cmdStream.cpp @@ -35,8 +35,30 @@ bool DivCSChannelState::doCall(unsigned int addr) { return true; } +unsigned char* DivCSPlayer::getData() { + return b; +} + +size_t DivCSPlayer::getDataLen() { + return bLen; +} + +DivCSChannelState* DivCSPlayer::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivCSPlayer::getFastDelays() { + return fastDelays; +} + +unsigned char* DivCSPlayer::getFastCmds() { + return fastCmds; +} + void DivCSPlayer::cleanup() { delete b; + b=NULL; + bLen=0; } bool DivCSPlayer::tick() { @@ -383,7 +405,8 @@ bool DivCSPlayer::init() { stream.readI(); continue; } - chan[i].readPos=stream.readI(); + chan[i].startPos=stream.readI(); + chan[i].readPos=chan[i].startPos; } stream.read(fastDelays,16); @@ -427,3 +450,17 @@ bool DivEngine::playStream(unsigned char* f, size_t length) { BUSY_END; return true; } + +DivCSPlayer* DivEngine::getStreamPlayer() { + return cmdStreamInt; +} + +bool DivEngine::killStream() { + if (!cmdStreamInt) return false; + BUSY_BEGIN; + cmdStreamInt->cleanup(); + delete cmdStreamInt; + cmdStreamInt=NULL; + BUSY_END; + return true; +} diff --git a/src/engine/cmdStream.h b/src/engine/cmdStream.h index cd0a786b7..8ecc17c67 100644 --- a/src/engine/cmdStream.h +++ b/src/engine/cmdStream.h @@ -26,6 +26,7 @@ class DivEngine; struct DivCSChannelState { + unsigned int startPos; unsigned int readPos; int waitTicks; @@ -69,6 +70,7 @@ struct DivCSChannelState { class DivCSPlayer { DivEngine* e; unsigned char* b; + size_t bLen; SafeReader stream; DivCSChannelState chan[DIV_MAX_CHANS]; unsigned char fastDelays[16]; @@ -77,12 +79,18 @@ class DivCSPlayer { short vibTable[64]; public: + unsigned char* getData(); + size_t getDataLen(); + DivCSChannelState* getChanState(int ch); + unsigned char* getFastDelays(); + unsigned char* getFastCmds(); void cleanup(); bool tick(); bool init(); DivCSPlayer(DivEngine* en, unsigned char* buf, size_t len): e(en), b(buf), + bLen(len), stream(buf,len) {} }; diff --git a/src/engine/engine.h b/src/engine/engine.h index 8e3bacdf2..049cce820 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -633,6 +633,10 @@ class DivEngine { bool load(unsigned char* f, size_t length); // play a binary command stream. bool playStream(unsigned char* f, size_t length); + // get the playing stream. + DivCSPlayer* getStreamPlayer(); + // destroy command stream player. + bool killStream(); // save as .dmf. SafeWriter* saveDMF(unsigned char version); // save as .fur. diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index aab72623f..bc62d2221 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1595,9 +1595,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { if (subticks==tickMult && cmdStreamInt) { if (!cmdStreamInt->tick()) { - cmdStreamInt->cleanup(); - delete cmdStreamInt; - cmdStreamInt=NULL; + // !!! } } diff --git a/src/engine/safeWriter.cpp b/src/engine/safeWriter.cpp index 0e447b2dc..c2388c460 100644 --- a/src/engine/safeWriter.cpp +++ b/src/engine/safeWriter.cpp @@ -227,3 +227,9 @@ void SafeWriter::finish() { buf=NULL; operative=false; } + +void SafeWriter::disown() { + if (!operative) return; + buf=NULL; + operative=false; +} diff --git a/src/engine/safeWriter.h b/src/engine/safeWriter.h index 640e78c47..666b0f1e2 100644 --- a/src/engine/safeWriter.h +++ b/src/engine/safeWriter.h @@ -62,6 +62,7 @@ class SafeWriter { void init(); SafeReader* toReader(); void finish(); + void disown(); SafeWriter(): operative(false), diff --git a/src/gui/csPlayer.cpp b/src/gui/csPlayer.cpp new file mode 100644 index 000000000..cec9ec2c0 --- /dev/null +++ b/src/gui/csPlayer.cpp @@ -0,0 +1,185 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 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 "gui.h" +#include +#include "imgui.h" + +void FurnaceGUI::drawCSPlayer() { + if (nextWindow==GUI_WINDOW_CS_PLAYER) { + csPlayerOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!csPlayerOpen) return; + if (ImGui::Begin("Command Stream Player",&csPlayerOpen,globalWinFlags)) { + if (ImGui::Button("Load")) { + openFileDialog(GUI_FILE_CMDSTREAM_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button("Kill")) { + if (!e->killStream()) { + showError("Kikai wa mou shindeiru!"); + } + } + ImGui::SameLine(); + if (ImGui::Button("Burn Current Song")) { + SafeWriter* w=e->saveCommand(true); + if (w!=NULL) { + if (!e->playStream(w->getFinalBuf(),w->size())) { + showError(e->getLastError()); + w->finish(); + delete w; + } else { + w->disown(); + delete w; + } + } + } + + DivCSPlayer* cs=e->getStreamPlayer(); + if (cs) { + if (ImGui::BeginTabBar("CSOptions")) { + int chans=e->getTotalChannelCount(); + if (ImGui::BeginTabItem("Status")) { + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Hex")) { + ImGui::PushFont(patFont); + if (ImGui::BeginTable("CSHexPos",chans,ImGuiTableFlags_SizingStretchSame)) { + ImGui::TableNextRow(); + for (int i=0; igetChanState(i); + ImGui::TableNextColumn(); + ImGui::Text("$%x",state->readPos); + } + ImGui::EndTable(); + } + + float oneCharSize=ImGui::CalcTextSize("A").x; + float threeCharSize=ImGui::CalcTextSize("AA").x; + float charViewSize=ImGui::CalcTextSize("0123456789ABCDEF").x; + + if (ImGui::BeginTable("CSHexPos",19)) { + char charView[17]; + ImGui::TableSetupScrollFreeze(1,1); + ImGui::TableSetupColumn("addr",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("d0",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d1",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d2",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d3",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d4",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d5",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d6",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d7",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d8",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d9",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d10",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d11",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d12",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d13",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d14",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("d15",ImGuiTableColumnFlags_WidthFixed,threeCharSize); + ImGui::TableSetupColumn("spacer",ImGuiTableColumnFlags_WidthFixed,oneCharSize); + ImGui::TableSetupColumn("char",ImGuiTableColumnFlags_WidthFixed,charViewSize); + + // header + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + for (int i=0; i<16; i++) { + ImGui::TableNextColumn(); + ImGui::Text("%X",i); + } + + // content + unsigned char* buf=cs->getData(); + size_t bufSize=cs->getDataLen(); + csClipper.Begin((bufSize+15)>>4,ImGui::GetTextLineHeightWithSpacing()); + while (csClipper.Step()) { + //std::vector highlightsUnsorted; + std::vector highlights; + int nextHighlight=-1; + int highlightPos=0; + + for (int i=0; igetChanState(i); + if ((int)state->readPos>=(csClipper.DisplayStart<<4) && (int)state->readPos<=(csClipper.DisplayEnd<<4)) { + highlights.push_back(state->readPos); + } + } + if (!highlights.empty()) nextHighlight=highlights[0]; + + + for (int i=csClipper.DisplayStart; i=(int)bufSize) continue; + if (pos==nextHighlight) { + ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(ImGuiCol_Header)); + highlightPos++; + if (highlightPos>=(int)highlights.size()) { + nextHighlight=-1; + } else { + nextHighlight=highlights[highlightPos]; + } + } + ImGui::Text("%.2X",buf[pos]); + } + + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + for (int j=0; j<16; j++) { + int pos=(i<<4)|j; + if (pos>=(int)bufSize) { + charView[j]=' '; + } else if (buf[pos]>=0x20 && buf[pos]<=0x7e) { + charView[j]=buf[pos]; + } else { + charView[j]='.'; + } + } + charView[16]=0; + + ImGui::TextUnformatted(charView); + } + } + csClipper.End(); + + ImGui::EndTable(); + } + ImGui::PopFont(); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_CS_PLAYER; + ImGui::End(); +} diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 98a2d9dc7..ed5057915 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -77,7 +77,7 @@ void FurnaceGUI::drawDebug() { ImGui::SameLine(); if (ImGui::Button("Pattern Advance")) e->haltWhen(DIV_HALT_PATTERN); - if (ImGui::Button("Play Command Stream")) openFileDialog(GUI_FILE_CMDSTREAM_OPEN); + if (ImGui::Button("Play Command Stream")) nextWindow=GUI_WINDOW_CS_PLAYER; if (ImGui::Button("Panic")) e->syncReset(); ImGui::SameLine(); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index c706410b3..fe0adc775 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -316,6 +316,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WINDOW_MEMORY: nextWindow=GUI_WINDOW_MEMORY; break; + case GUI_ACTION_WINDOW_CS_PLAYER: + nextWindow=GUI_WINDOW_CS_PLAYER; + break; case GUI_ACTION_COLLAPSE_WINDOW: collapseWindow=true; @@ -418,6 +421,9 @@ void FurnaceGUI::doAction(int what) { case GUI_WINDOW_MEMORY: memoryOpen=false; break; + case GUI_WINDOW_CS_PLAYER: + csPlayerOpen=false; + break; default: break; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index c9593d7c1..ed0d437e3 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3463,6 +3463,7 @@ bool FurnaceGUI::loop() { DECLARE_METRIC(volMeter) DECLARE_METRIC(settings) DECLARE_METRIC(debug) + DECLARE_METRIC(csPlayer) DECLARE_METRIC(stats) DECLARE_METRIC(memory) DECLARE_METRIC(compatFlags) @@ -4046,6 +4047,7 @@ bool FurnaceGUI::loop() { IMPORT_CLOSE(groovesOpen); IMPORT_CLOSE(xyOscOpen); IMPORT_CLOSE(memoryOpen); + IMPORT_CLOSE(csPlayerOpen); } else if (pendingLayoutImportStep==1) { // let the UI settle } else if (pendingLayoutImportStep==2) { @@ -4629,6 +4631,7 @@ bool FurnaceGUI::loop() { globalWinFlags=0; MEASURE(settings,drawSettings()); MEASURE(debug,drawDebug()); + MEASURE(csPlayer,drawCSPlayer()); MEASURE(log,drawLog()); MEASURE(compatFlags,drawCompatFlags()); MEASURE(stats,drawStats()); @@ -4669,6 +4672,7 @@ bool FurnaceGUI::loop() { MEASURE(volMeter,drawVolMeter()); MEASURE(settings,drawSettings()); MEASURE(debug,drawDebug()); + MEASURE(csPlayer,drawCSPlayer()); MEASURE(stats,drawStats()); MEASURE(memory,drawMemory()); MEASURE(compatFlags,drawCompatFlags()); @@ -7495,6 +7499,7 @@ FurnaceGUI::FurnaceGUI(): groovesOpen(false), xyOscOpen(false), memoryOpen(false), + csPlayerOpen(false), shortIntro(false), insListDir(false), waveListDir(false), diff --git a/src/gui/gui.h b/src/gui/gui.h index 1b036bae5..3b1a2b552 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -478,6 +478,7 @@ enum FurnaceGUIWindows { GUI_WINDOW_XY_OSC, GUI_WINDOW_INTRO_MON, GUI_WINDOW_MEMORY, + GUI_WINDOW_CS_PLAYER, GUI_WINDOW_SPOILER }; @@ -669,6 +670,7 @@ enum FurnaceGUIActions { GUI_ACTION_WINDOW_GROOVES, GUI_ACTION_WINDOW_XY_OSC, GUI_ACTION_WINDOW_MEMORY, + GUI_ACTION_WINDOW_CS_PLAYER, GUI_ACTION_COLLAPSE_WINDOW, GUI_ACTION_CLOSE_WINDOW, @@ -2064,7 +2066,7 @@ class FurnaceGUI { bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen; bool subSongsOpen, findOpen, spoilerOpen, patManagerOpen, sysManagerOpen, clockOpen, speedOpen; - bool groovesOpen, xyOscOpen, memoryOpen; + bool groovesOpen, xyOscOpen, memoryOpen, csPlayerOpen; bool shortIntro; bool insListDir, waveListDir, sampleListDir; @@ -2449,6 +2451,9 @@ class FurnaceGUI { // tutorial int curTutorial, curTutorialStep; + // command stream player + ImGuiListClipper csClipper; + // export options int audioExportType; FurnaceGUIExportTypes curExportType; @@ -2573,6 +2578,7 @@ class FurnaceGUI { void drawIntro(double introTime, bool monitor=false); void drawSettings(); void drawDebug(); + void drawCSPlayer(); void drawNewSong(); void drawPalette(); void drawExport(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index b17688032..fc4916229 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -606,6 +606,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WINDOW_GROOVES", "Grooves", 0), D("WINDOW_XY_OSC", "Oscilloscope (X-Y)", 0), D("WINDOW_MEMORY", "Memory Compositiom", 0), + D("WINDOW_CS_PLAYER", "Command Stream Player", 0), D("COLLAPSE_WINDOW", "Collapse/expand current window", 0), D("CLOSE_WINDOW", "Close current window", FURKMOD_SHIFT|SDLK_ESCAPE), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 283862ee2..f19dc9d04 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1759,6 +1759,7 @@ void FurnaceGUI::drawSettings() { UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_MEMORY); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_EFFECT_LIST); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_DEBUG); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_CS_PLAYER); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_ABOUT); UI_KEYBIND_CONFIG(GUI_ACTION_COLLAPSE_WINDOW); UI_KEYBIND_CONFIG(GUI_ACTION_CLOSE_WINDOW);