From df63257d117066797d727c2b27343e90dacdcaa9 Mon Sep 17 00:00:00 2001 From: Eknous-P Date: Tue, 5 Dec 2023 15:58:45 +0400 Subject: [PATCH 01/53] dummy export window, keybind and related settings --- CMakeLists.txt | 1 + src/gui/doAction.cpp | 3 + src/gui/exportWin.cpp | 24 +++ src/gui/gui.cpp | 358 ++++++++++++++++++++++-------------------- src/gui/gui.h | 6 +- src/gui/guiConst.cpp | 1 + src/gui/settings.cpp | 10 ++ 7 files changed, 233 insertions(+), 170 deletions(-) create mode 100644 src/gui/exportWin.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f1941a02b..5336f3131 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -779,6 +779,7 @@ src/gui/doAction.cpp src/gui/editing.cpp src/gui/editControls.cpp src/gui/effectList.cpp +src/gui/exportWin.cpp src/gui/findReplace.cpp src/gui/fmPreview.cpp src/gui/gradient.cpp diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 0d1bcf623..7a30760cf 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -65,6 +65,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAVE_AS: openFileDialog(GUI_FILE_SAVE); break; + case GUI_ACTION_EXPORT: + displayExport=true; + break; case GUI_ACTION_UNDO: if (curWindow==GUI_WINDOW_SAMPLE_EDIT) { doUndoSample(); diff --git a/src/gui/exportWin.cpp b/src/gui/exportWin.cpp new file mode 100644 index 000000000..6e2c8b559 --- /dev/null +++ b/src/gui/exportWin.cpp @@ -0,0 +1,24 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 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" + +void FurnaceGUI::drawExport() { + if (ImGui::Button("dummy")) ImGui::CloseCurrentPopup(); +} diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 86cf09a86..99c7297ab 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4081,197 +4081,203 @@ bool FurnaceGUI::loop() { openFileDialog(GUI_FILE_SAVE_DMF_LEGACY); } ImGui::Separator(); - if (ImGui::BeginMenu("export audio...")) { - exitDisabledTimer=1; - if (ImGui::MenuItem("one file")) { - openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); + if (settings.classicExportOptions) { + if (ImGui::BeginMenu("export audio...")) { + exitDisabledTimer=1; + if (ImGui::MenuItem("one file")) { + openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); + } + if (ImGui::MenuItem("multiple files (one per chip)")) { + openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); + } + if (ImGui::MenuItem("multiple files (one per channel)")) { + openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL); + } + if (ImGui::InputInt("Loops",&exportLoops,1,2)) { + if (exportLoops<0) exportLoops=0; + } + if (ImGui::InputDouble("Fade out (seconds)",&exportFadeOut,1.0,2.0,"%.1f")) { + if (exportFadeOut<0.0) exportFadeOut=0.0; + } + ImGui::EndMenu(); } - if (ImGui::MenuItem("multiple files (one per chip)")) { - openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); - } - if (ImGui::MenuItem("multiple files (one per channel)")) { - openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL); - } - if (ImGui::InputInt("Loops",&exportLoops,1,2)) { - if (exportLoops<0) exportLoops=0; - } - if (ImGui::InputDouble("Fade out (seconds)",&exportFadeOut,1.0,2.0,"%.1f")) { - if (exportFadeOut<0.0) exportFadeOut=0.0; - } - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("export VGM...")) { - exitDisabledTimer=1; - ImGui::Text("settings:"); - if (ImGui::BeginCombo("format version",fmt::sprintf("%d.%.2x",vgmExportVersion>>8,vgmExportVersion&0xff).c_str())) { - for (int i=0; i<7; i++) { - if (ImGui::Selectable(fmt::sprintf("%d.%.2x",vgmVersions[i]>>8,vgmVersions[i]&0xff).c_str(),vgmExportVersion==vgmVersions[i])) { - vgmExportVersion=vgmVersions[i]; + if (ImGui::BeginMenu("export VGM...")) { + exitDisabledTimer=1; + ImGui::Text("settings:"); + if (ImGui::BeginCombo("format version",fmt::sprintf("%d.%.2x",vgmExportVersion>>8,vgmExportVersion&0xff).c_str())) { + for (int i=0; i<7; i++) { + if (ImGui::Selectable(fmt::sprintf("%d.%.2x",vgmVersions[i]>>8,vgmVersions[i]&0xff).c_str(),vgmExportVersion==vgmVersions[i])) { + vgmExportVersion=vgmVersions[i]; + } } + ImGui::EndCombo(); } - ImGui::EndCombo(); - } - ImGui::Checkbox("loop",&vgmExportLoop); - if (vgmExportLoop && e->song.loopModality==2) { - ImGui::Text("loop trail:"); - ImGui::Indent(); - if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) { - vgmExportTrailingTicks=-1; - } - if (ImGui::RadioButton("add one loop",vgmExportTrailingTicks==-2)) { - vgmExportTrailingTicks=-2; - } - if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) { - vgmExportTrailingTicks=0; - } - if (vgmExportTrailingTicks>=0) { - ImGui::SameLine(); - if (ImGui::InputInt("##TrailTicks",&vgmExportTrailingTicks,1,100)) { - if (vgmExportTrailingTicks<0) vgmExportTrailingTicks=0; + ImGui::Checkbox("loop",&vgmExportLoop); + if (vgmExportLoop && e->song.loopModality==2) { + ImGui::Text("loop trail:"); + ImGui::Indent(); + if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) { + vgmExportTrailingTicks=-1; } + if (ImGui::RadioButton("add one loop",vgmExportTrailingTicks==-2)) { + vgmExportTrailingTicks=-2; + } + if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) { + vgmExportTrailingTicks=0; + } + if (vgmExportTrailingTicks>=0) { + ImGui::SameLine(); + if (ImGui::InputInt("##TrailTicks",&vgmExportTrailingTicks,1,100)) { + if (vgmExportTrailingTicks<0) vgmExportTrailingTicks=0; + } + } + ImGui::Unindent(); } - ImGui::Unindent(); - } - ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip( - "inserts data blocks on pattern changes.\n" - "useful if you are writing a playback routine.\n\n" + ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "inserts data blocks on pattern changes.\n" + "useful if you are writing a playback routine.\n\n" - "the format of a pattern change data block is:\n" - "67 66 FE ll ll ll ll 01 oo rr pp pp pp ...\n" - "- ll: length, a 32-bit little-endian number\n" - "- oo: order\n" - "- rr: initial row (a 0Dxx effect is able to select a different row)\n" - "- pp: pattern index (one per channel)\n\n" + "the format of a pattern change data block is:\n" + "67 66 FE ll ll ll ll 01 oo rr pp pp pp ...\n" + "- ll: length, a 32-bit little-endian number\n" + "- oo: order\n" + "- rr: initial row (a 0Dxx effect is able to select a different row)\n" + "- pp: pattern index (one per channel)\n\n" - "pattern indexes are ordered as they appear in the song." - ); - } - ImGui::Checkbox("direct stream mode",&vgmExportDirectStream); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip( - "required for DualPCM and MSM6258 export.\n\n" - "allows for volume/direction changes when playing samples,\n" - "at the cost of a massive increase in file size." - ); - } - ImGui::Text("chips to export:"); - bool hasOneAtLeast=false; - for (int i=0; isong.systemLen; i++) { - int minVersion=e->minVGMVersion(e->song.system[i]); - ImGui::BeginDisabled(minVersion>vgmExportVersion || minVersion==0); - ImGui::Checkbox(fmt::sprintf("%d. %s##_SYSV%d",i+1,getSystemName(e->song.system[i]),i).c_str(),&willExport[i]); - ImGui::EndDisabled(); - if (minVersion>vgmExportVersion) { - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetTooltip("this chip is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); + "pattern indexes are ordered as they appear in the song." + ); + } + ImGui::Checkbox("direct stream mode",&vgmExportDirectStream); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "required for DualPCM and MSM6258 export.\n\n" + "allows for volume/direction changes when playing samples,\n" + "at the cost of a massive increase in file size." + ); + } + ImGui::Text("chips to export:"); + bool hasOneAtLeast=false; + for (int i=0; isong.systemLen; i++) { + int minVersion=e->minVGMVersion(e->song.system[i]); + ImGui::BeginDisabled(minVersion>vgmExportVersion || minVersion==0); + ImGui::Checkbox(fmt::sprintf("%d. %s##_SYSV%d",i+1,getSystemName(e->song.system[i]),i).c_str(),&willExport[i]); + ImGui::EndDisabled(); + if (minVersion>vgmExportVersion) { + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + ImGui::SetTooltip("this chip is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); + } + } else if (minVersion==0) { + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + ImGui::SetTooltip("this chip is not supported by the VGM format!"); + } + } else { + if (willExport[i]) hasOneAtLeast=true; } - } else if (minVersion==0) { - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetTooltip("this chip is not supported by the VGM format!"); + } + ImGui::Text("select the chip you wish to export,"); + ImGui::Text("but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); + if (hasOneAtLeast) { + if (ImGui::MenuItem("click to export")) { + openFileDialog(GUI_FILE_EXPORT_VGM); } } else { - if (willExport[i]) hasOneAtLeast=true; - } - } - ImGui::Text("select the chip you wish to export,"); - ImGui::Text("but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); - if (hasOneAtLeast) { - if (ImGui::MenuItem("click to export")) { - openFileDialog(GUI_FILE_EXPORT_VGM); - } - } else { - ImGui::Text("nothing to export"); - } - ImGui::EndMenu(); - } - int numZSMCompat=0; - for (int i=0; isong.systemLen; i++) { - if ((e->song.system[i] == DIV_SYSTEM_VERA) || (e->song.system[i] == DIV_SYSTEM_YM2151)) numZSMCompat++; - } - if (numZSMCompat > 0) { - if (ImGui::BeginMenu("export ZSM...")) { - exitDisabledTimer=1; - ImGui::Text("Commander X16 Zsound Music File"); - if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) { - if (zsmExportTickRate<1) zsmExportTickRate=1; - if (zsmExportTickRate>44100) zsmExportTickRate=44100; - } - ImGui::Checkbox("loop",&zsmExportLoop); - ImGui::SameLine(); - ImGui::Checkbox("optimize size",&zsmExportOptimize); - ImGui::SameLine(); - if (ImGui::Button("Begin Export")) { - openFileDialog(GUI_FILE_EXPORT_ZSM); - ImGui::CloseCurrentPopup(); + ImGui::Text("nothing to export"); } ImGui::EndMenu(); } - } - int numAmiga=0; - for (int i=0; isong.systemLen; i++) { - if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; - } - if (numAmiga && settings.iCannotWait) { - if (ImGui::BeginMenu("export Amiga validation data...")) { + int numZSMCompat=0; + for (int i=0; isong.systemLen; i++) { + if ((e->song.system[i] == DIV_SYSTEM_VERA) || (e->song.system[i] == DIV_SYSTEM_YM2151)) numZSMCompat++; + } + if (numZSMCompat > 0) { + if (ImGui::BeginMenu("export ZSM...")) { + exitDisabledTimer=1; + ImGui::Text("Commander X16 Zsound Music File"); + if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) { + if (zsmExportTickRate<1) zsmExportTickRate=1; + if (zsmExportTickRate>44100) zsmExportTickRate=44100; + } + ImGui::Checkbox("loop",&zsmExportLoop); + ImGui::SameLine(); + ImGui::Checkbox("optimize size",&zsmExportOptimize); + ImGui::SameLine(); + if (ImGui::Button("Begin Export")) { + openFileDialog(GUI_FILE_EXPORT_ZSM); + ImGui::CloseCurrentPopup(); + } + ImGui::EndMenu(); + } + } + int numAmiga=0; + for (int i=0; isong.systemLen; i++) { + if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; + } + if (numAmiga && settings.iCannotWait) { + if (ImGui::BeginMenu("export Amiga validation data...")) { + exitDisabledTimer=1; + ImGui::Text( + "this is NOT ROM export! only use for making sure the\n" + "Furnace Amiga emulator is working properly by\n" + "comparing it with real Amiga output." + ); + ImGui::AlignTextToFramePadding(); + ImGui::Text("Directory"); + ImGui::SameLine(); + ImGui::InputText("##AVDPath",&workingDirROMExport); + if (ImGui::Button("Bake Data")) { + std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); + if (workingDirROMExport.size()>0) { + if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; + } + for (DivROMExportOutput& i: out) { + String path=workingDirROMExport+i.name; + FILE* outFile=ps_fopen(path.c_str(),"wb"); + if (outFile!=NULL) { + fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); + fclose(outFile); + } + i.data->finish(); + delete i.data; + } + showError(fmt::sprintf("Done! Baked %d files.",(int)out.size())); + ImGui::CloseCurrentPopup(); + } + ImGui::EndMenu(); + } + } + if (ImGui::BeginMenu("export text...")) { exitDisabledTimer=1; ImGui::Text( - "this is NOT ROM export! only use for making sure the\n" - "Furnace Amiga emulator is working properly by\n" - "comparing it with real Amiga output." + "this option exports the song to a text file.\n" ); - ImGui::AlignTextToFramePadding(); - ImGui::Text("Directory"); - ImGui::SameLine(); - ImGui::InputText("##AVDPath",&workingDirROMExport); - if (ImGui::Button("Bake Data")) { - std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); - if (workingDirROMExport.size()>0) { - if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; - } - for (DivROMExportOutput& i: out) { - String path=workingDirROMExport+i.name; - FILE* outFile=ps_fopen(path.c_str(),"wb"); - if (outFile!=NULL) { - fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); - fclose(outFile); - } - i.data->finish(); - delete i.data; - } - showError(fmt::sprintf("Done! Baked %d files.",(int)out.size())); - ImGui::CloseCurrentPopup(); + if (ImGui::Button("export")) { + openFileDialog(GUI_FILE_EXPORT_TEXT); } ImGui::EndMenu(); } - } - if (ImGui::BeginMenu("export text...")) { - exitDisabledTimer=1; - ImGui::Text( - "this option exports the song to a text file.\n" - ); - if (ImGui::Button("export")) { - openFileDialog(GUI_FILE_EXPORT_TEXT); - } - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("export command stream...")) { - exitDisabledTimer=1; - 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" + if (ImGui::BeginMenu("export command stream...")) { + exitDisabledTimer=1; + 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 (binary)")) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); + "technical/development use only!" + ); + if (ImGui::Button("export (binary)")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); + } + if (ImGui::Button("export (text)")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); + } + ImGui::EndMenu(); } - if (ImGui::Button("export (text)")) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); + } else { + if (ImGui::MenuItem("export...",BIND_FOR(GUI_ACTION_EXPORT))) { + displayExport=true; } - ImGui::EndMenu(); } ImGui::Separator(); if (!settings.classicChipOptions) { @@ -5453,6 +5459,11 @@ bool FurnaceGUI::loop() { } } + if (displayExport) { + displayExport=false; + ImGui::OpenPopup("Export"); + } + if (displayEditString) { ImGui::OpenPopup("EditString"); } @@ -5494,6 +5505,15 @@ bool FurnaceGUI::loop() { ImGui::EndPopup(); } + if (ImGui::BeginPopupModal("Export",NULL,ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) { + ImGui::SetWindowPos(ImVec2(((canvasW)-ImGui::GetWindowSize().x)*0.5,((canvasH)-ImGui::GetWindowSize().y)*0.5)); + if (ImGui::GetWindowSize().xgetConfInt("centerPopup",1); settings.insIconsStyle=e->getConfInt("insIconsStyle",1); settings.classicChipOptions=e->getConfInt("classicChipOptions",0); + settings.classicExportOptions=e->getConfInt("classicExportOptions",0); settings.wasapiEx=e->getConfInt("wasapiEx",0); settings.chanOscThreads=e->getConfInt("chanOscThreads",0); settings.renderPoolThreads=e->getConfInt("renderPoolThreads",0); @@ -3953,6 +3961,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.centerPopup,0,1); clampSetting(settings.insIconsStyle,0,2); clampSetting(settings.classicChipOptions,0,1); + clampSetting(settings.classicExportOptions,0,1); clampSetting(settings.wasapiEx,0,1); clampSetting(settings.chanOscThreads,0,256); clampSetting(settings.renderPoolThreads,0,DIV_MAX_CHIPS); @@ -4232,6 +4241,7 @@ void FurnaceGUI::commitSettings() { e->setConf("centerPopup",settings.centerPopup); e->setConf("insIconsStyle",settings.insIconsStyle); e->setConf("classicChipOptions",settings.classicChipOptions); + e->setConf("classicExportOptions",settings.classicExportOptions); e->setConf("wasapiEx",settings.wasapiEx); e->setConf("chanOscThreads",settings.chanOscThreads); e->setConf("renderPoolThreads",settings.renderPoolThreads); From b90132d7330876b63a34ae0916014b70f20caa08 Mon Sep 17 00:00:00 2001 From: Eknous-P Date: Tue, 5 Dec 2023 16:42:08 +0400 Subject: [PATCH 02/53] the export types wtf is settings.iCannotWait ??? --- src/gui/exportWin.cpp | 208 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 207 insertions(+), 1 deletion(-) diff --git a/src/gui/exportWin.cpp b/src/gui/exportWin.cpp index 6e2c8b559..796412ba3 100644 --- a/src/gui/exportWin.cpp +++ b/src/gui/exportWin.cpp @@ -18,7 +18,213 @@ */ #include "gui.h" +#include "guiConst.h" +#include "../fileutils.h" +#include "misc/cpp/imgui_stdlib.h" void FurnaceGUI::drawExport() { - if (ImGui::Button("dummy")) ImGui::CloseCurrentPopup(); + exitDisabledTimer=1; + if (ImGui::BeginTabBar("ExportTypes")) { + if (ImGui::BeginTabItem("Audio")) { + static int audioExportType=0; + ImGui::RadioButton("one file",&audioExportType,0); + ImGui::RadioButton("multiple files (one per chip)",&audioExportType,1); + ImGui::RadioButton("multiple files (one per channel)",&audioExportType,2); + if (ImGui::InputInt("Loops",&exportLoops,1,2)) { + if (exportLoops<0) exportLoops=0; + } + if (ImGui::InputDouble("Fade out (seconds)",&exportFadeOut,1.0,2.0,"%.1f")) { + if (exportFadeOut<0.0) exportFadeOut=0.0; + } + + if (ImGui::Button("Export")) { + switch (audioExportType) { + case 0: + openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); + break; + case 1: + openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); + break; + case 2: + openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL); + break; + } + ImGui::CloseCurrentPopup(); + } + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("VGM")) { + ImGui::Text("settings:"); + if (ImGui::BeginCombo("format version",fmt::sprintf("%d.%.2x",vgmExportVersion>>8,vgmExportVersion&0xff).c_str())) { + for (int i=0; i<7; i++) { + if (ImGui::Selectable(fmt::sprintf("%d.%.2x",vgmVersions[i]>>8,vgmVersions[i]&0xff).c_str(),vgmExportVersion==vgmVersions[i])) { + vgmExportVersion=vgmVersions[i]; + } + } + ImGui::EndCombo(); + } + ImGui::Checkbox("loop",&vgmExportLoop); + if (vgmExportLoop && e->song.loopModality==2) { + ImGui::Text("loop trail:"); + ImGui::Indent(); + if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) { + vgmExportTrailingTicks=-1; + } + if (ImGui::RadioButton("add one loop",vgmExportTrailingTicks==-2)) { + vgmExportTrailingTicks=-2; + } + if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) { + vgmExportTrailingTicks=0; + } + if (vgmExportTrailingTicks>=0) { + ImGui::SameLine(); + if (ImGui::InputInt("##TrailTicks",&vgmExportTrailingTicks,1,100)) { + if (vgmExportTrailingTicks<0) vgmExportTrailingTicks=0; + } + } + ImGui::Unindent(); + } + ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "inserts data blocks on pattern changes.\n" + "useful if you are writing a playback routine.\n\n" + + "the format of a pattern change data block is:\n" + "67 66 FE ll ll ll ll 01 oo rr pp pp pp ...\n" + "- ll: length, a 32-bit little-endian number\n" + "- oo: order\n" + "- rr: initial row (a 0Dxx effect is able to select a different row)\n" + "- pp: pattern index (one per channel)\n\n" + + "pattern indexes are ordered as they appear in the song." + ); + } + ImGui::Checkbox("direct stream mode",&vgmExportDirectStream); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "required for DualPCM and MSM6258 export.\n\n" + "allows for volume/direction changes when playing samples,\n" + "at the cost of a massive increase in file size." + ); + } + ImGui::Text("chips to export:"); + bool hasOneAtLeast=false; + for (int i=0; isong.systemLen; i++) { + int minVersion=e->minVGMVersion(e->song.system[i]); + ImGui::BeginDisabled(minVersion>vgmExportVersion || minVersion==0); + ImGui::Checkbox(fmt::sprintf("%d. %s##_SYSV%d",i+1,getSystemName(e->song.system[i]),i).c_str(),&willExport[i]); + ImGui::EndDisabled(); + if (minVersion>vgmExportVersion) { + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + ImGui::SetTooltip("this chip is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); + } + } else if (minVersion==0) { + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + ImGui::SetTooltip("this chip is not supported by the VGM format!"); + } + } else { + if (willExport[i]) hasOneAtLeast=true; + } + } + ImGui::Text("select the chip you wish to export, but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); + if (hasOneAtLeast) { + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_VGM); + ImGui::CloseCurrentPopup(); + } + } else { + ImGui::Text("nothing to export"); + } + ImGui::EndTabItem(); + } + int numZSMCompat=0; + for (int i=0; isong.systemLen; i++) { + if ((e->song.system[i] == DIV_SYSTEM_VERA) || (e->song.system[i] == DIV_SYSTEM_YM2151)) numZSMCompat++; + } + if (numZSMCompat > 0) { + if (ImGui::BeginTabItem("ZSM")) { + ImGui::Text("Commander X16 Zsound Music File"); + if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) { + if (zsmExportTickRate<1) zsmExportTickRate=1; + if (zsmExportTickRate>44100) zsmExportTickRate=44100; + } + ImGui::Checkbox("loop",&zsmExportLoop); + ImGui::SameLine(); + ImGui::Checkbox("optimize size",&zsmExportOptimize); + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_ZSM); + ImGui::CloseCurrentPopup(); + } + ImGui::EndTabItem(); + } + } + int numAmiga=0; + for (int i=0; isong.systemLen; i++) { + if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; + } + if (numAmiga && settings.iCannotWait) { + if (ImGui::BeginTabItem("Amiga Validation")) { + ImGui::Text( + "this is NOT ROM export! only use for making sure the\n" + "Furnace Amiga emulator is working properly by\n" + "comparing it with real Amiga output." + ); + ImGui::AlignTextToFramePadding(); + ImGui::Text("Directory"); + ImGui::SameLine(); + ImGui::InputText("##AVDPath",&workingDirROMExport); + if (ImGui::Button("Bake Data")) { + std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); + if (workingDirROMExport.size()>0) { + if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; + } + for (DivROMExportOutput& i: out) { + String path=workingDirROMExport+i.name; + FILE* outFile=ps_fopen(path.c_str(),"wb"); + if (outFile!=NULL) { + fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); + fclose(outFile); + } + i.data->finish(); + delete i.data; + } + showError(fmt::sprintf("Done! Baked %d files.",(int)out.size())); + ImGui::CloseCurrentPopup(); + } + ImGui::EndTabItem(); + } + } + if (ImGui::BeginTabItem("Text")) { + ImGui::Text( + "this option exports the song to a text file.\n" + ); + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_TEXT); + ImGui::CloseCurrentPopup(); + } + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("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 (binary)")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Export (text)")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); + ImGui::CloseCurrentPopup(); + } + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + ImGui::Separator(); + if (ImGui::Button("Cancel")) ImGui::CloseCurrentPopup(); } From d2ca97c57a09a06893135fe0559a908c8f642e00 Mon Sep 17 00:00:00 2001 From: Eknous-P Date: Tue, 5 Dec 2023 18:00:14 +0400 Subject: [PATCH 03/53] put the types in a child now the cancel button is neatly at the bottom :) --- src/gui/exportWin.cpp | 361 +++++++++++++++++++++--------------------- 1 file changed, 184 insertions(+), 177 deletions(-) diff --git a/src/gui/exportWin.cpp b/src/gui/exportWin.cpp index 796412ba3..ed1cc2f34 100644 --- a/src/gui/exportWin.cpp +++ b/src/gui/exportWin.cpp @@ -24,206 +24,213 @@ void FurnaceGUI::drawExport() { exitDisabledTimer=1; - if (ImGui::BeginTabBar("ExportTypes")) { - if (ImGui::BeginTabItem("Audio")) { - static int audioExportType=0; - ImGui::RadioButton("one file",&audioExportType,0); - ImGui::RadioButton("multiple files (one per chip)",&audioExportType,1); - ImGui::RadioButton("multiple files (one per channel)",&audioExportType,2); - if (ImGui::InputInt("Loops",&exportLoops,1,2)) { - if (exportLoops<0) exportLoops=0; - } - if (ImGui::InputDouble("Fade out (seconds)",&exportFadeOut,1.0,2.0,"%.1f")) { - if (exportFadeOut<0.0) exportFadeOut=0.0; - } - if (ImGui::Button("Export")) { - switch (audioExportType) { - case 0: - openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); - break; - case 1: - openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); - break; - case 2: - openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL); - break; + ImVec2 avail=ImGui::GetContentRegionAvail(); + avail.y-=ImGui::GetFrameHeightWithSpacing(); + + if (ImGui::BeginChild("sysPickerC",avail,false,ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) { + if (ImGui::BeginTabBar("ExportTypes")) { + if (ImGui::BeginTabItem("Audio")) { + static int audioExportType=0; + ImGui::RadioButton("one file",&audioExportType,0); + ImGui::RadioButton("multiple files (one per chip)",&audioExportType,1); + ImGui::RadioButton("multiple files (one per channel)",&audioExportType,2); + if (ImGui::InputInt("Loops",&exportLoops,1,2)) { + if (exportLoops<0) exportLoops=0; } - ImGui::CloseCurrentPopup(); + if (ImGui::InputDouble("Fade out (seconds)",&exportFadeOut,1.0,2.0,"%.1f")) { + if (exportFadeOut<0.0) exportFadeOut=0.0; + } + + if (ImGui::Button("Export")) { + switch (audioExportType) { + case 0: + openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); + break; + case 1: + openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); + break; + case 2: + openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL); + break; + } + ImGui::CloseCurrentPopup(); + } + ImGui::EndTabItem(); } - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("VGM")) { - ImGui::Text("settings:"); - if (ImGui::BeginCombo("format version",fmt::sprintf("%d.%.2x",vgmExportVersion>>8,vgmExportVersion&0xff).c_str())) { - for (int i=0; i<7; i++) { - if (ImGui::Selectable(fmt::sprintf("%d.%.2x",vgmVersions[i]>>8,vgmVersions[i]&0xff).c_str(),vgmExportVersion==vgmVersions[i])) { - vgmExportVersion=vgmVersions[i]; + if (ImGui::BeginTabItem("VGM")) { + ImGui::Text("settings:"); + if (ImGui::BeginCombo("format version",fmt::sprintf("%d.%.2x",vgmExportVersion>>8,vgmExportVersion&0xff).c_str())) { + for (int i=0; i<7; i++) { + if (ImGui::Selectable(fmt::sprintf("%d.%.2x",vgmVersions[i]>>8,vgmVersions[i]&0xff).c_str(),vgmExportVersion==vgmVersions[i])) { + vgmExportVersion=vgmVersions[i]; + } } + ImGui::EndCombo(); } - ImGui::EndCombo(); - } - ImGui::Checkbox("loop",&vgmExportLoop); - if (vgmExportLoop && e->song.loopModality==2) { - ImGui::Text("loop trail:"); - ImGui::Indent(); - if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) { - vgmExportTrailingTicks=-1; - } - if (ImGui::RadioButton("add one loop",vgmExportTrailingTicks==-2)) { - vgmExportTrailingTicks=-2; - } - if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) { - vgmExportTrailingTicks=0; - } - if (vgmExportTrailingTicks>=0) { - ImGui::SameLine(); - if (ImGui::InputInt("##TrailTicks",&vgmExportTrailingTicks,1,100)) { - if (vgmExportTrailingTicks<0) vgmExportTrailingTicks=0; + ImGui::Checkbox("loop",&vgmExportLoop); + if (vgmExportLoop && e->song.loopModality==2) { + ImGui::Text("loop trail:"); + ImGui::Indent(); + if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) { + vgmExportTrailingTicks=-1; } + if (ImGui::RadioButton("add one loop",vgmExportTrailingTicks==-2)) { + vgmExportTrailingTicks=-2; + } + if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) { + vgmExportTrailingTicks=0; + } + if (vgmExportTrailingTicks>=0) { + ImGui::SameLine(); + if (ImGui::InputInt("##TrailTicks",&vgmExportTrailingTicks,1,100)) { + if (vgmExportTrailingTicks<0) vgmExportTrailingTicks=0; + } + } + ImGui::Unindent(); } - ImGui::Unindent(); - } - ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip( - "inserts data blocks on pattern changes.\n" - "useful if you are writing a playback routine.\n\n" + ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "inserts data blocks on pattern changes.\n" + "useful if you are writing a playback routine.\n\n" - "the format of a pattern change data block is:\n" - "67 66 FE ll ll ll ll 01 oo rr pp pp pp ...\n" - "- ll: length, a 32-bit little-endian number\n" - "- oo: order\n" - "- rr: initial row (a 0Dxx effect is able to select a different row)\n" - "- pp: pattern index (one per channel)\n\n" + "the format of a pattern change data block is:\n" + "67 66 FE ll ll ll ll 01 oo rr pp pp pp ...\n" + "- ll: length, a 32-bit little-endian number\n" + "- oo: order\n" + "- rr: initial row (a 0Dxx effect is able to select a different row)\n" + "- pp: pattern index (one per channel)\n\n" - "pattern indexes are ordered as they appear in the song." - ); - } - ImGui::Checkbox("direct stream mode",&vgmExportDirectStream); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip( - "required for DualPCM and MSM6258 export.\n\n" - "allows for volume/direction changes when playing samples,\n" - "at the cost of a massive increase in file size." - ); - } - ImGui::Text("chips to export:"); - bool hasOneAtLeast=false; - for (int i=0; isong.systemLen; i++) { - int minVersion=e->minVGMVersion(e->song.system[i]); - ImGui::BeginDisabled(minVersion>vgmExportVersion || minVersion==0); - ImGui::Checkbox(fmt::sprintf("%d. %s##_SYSV%d",i+1,getSystemName(e->song.system[i]),i).c_str(),&willExport[i]); - ImGui::EndDisabled(); - if (minVersion>vgmExportVersion) { - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetTooltip("this chip is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); + "pattern indexes are ordered as they appear in the song." + ); + } + ImGui::Checkbox("direct stream mode",&vgmExportDirectStream); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "required for DualPCM and MSM6258 export.\n\n" + "allows for volume/direction changes when playing samples,\n" + "at the cost of a massive increase in file size." + ); + } + ImGui::Text("chips to export:"); + bool hasOneAtLeast=false; + for (int i=0; isong.systemLen; i++) { + int minVersion=e->minVGMVersion(e->song.system[i]); + ImGui::BeginDisabled(minVersion>vgmExportVersion || minVersion==0); + ImGui::Checkbox(fmt::sprintf("%d. %s##_SYSV%d",i+1,getSystemName(e->song.system[i]),i).c_str(),&willExport[i]); + ImGui::EndDisabled(); + if (minVersion>vgmExportVersion) { + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + ImGui::SetTooltip("this chip is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); + } + } else if (minVersion==0) { + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + ImGui::SetTooltip("this chip is not supported by the VGM format!"); + } + } else { + if (willExport[i]) hasOneAtLeast=true; } - } else if (minVersion==0) { - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetTooltip("this chip is not supported by the VGM format!"); + } + ImGui::Text("select the chip you wish to export, but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); + if (hasOneAtLeast) { + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_VGM); + ImGui::CloseCurrentPopup(); } } else { - if (willExport[i]) hasOneAtLeast=true; + ImGui::Text("nothing to export"); } - } - ImGui::Text("select the chip you wish to export, but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); - if (hasOneAtLeast) { - if (ImGui::Button("Export")) { - openFileDialog(GUI_FILE_EXPORT_VGM); - ImGui::CloseCurrentPopup(); - } - } else { - ImGui::Text("nothing to export"); - } - ImGui::EndTabItem(); - } - int numZSMCompat=0; - for (int i=0; isong.systemLen; i++) { - if ((e->song.system[i] == DIV_SYSTEM_VERA) || (e->song.system[i] == DIV_SYSTEM_YM2151)) numZSMCompat++; - } - if (numZSMCompat > 0) { - if (ImGui::BeginTabItem("ZSM")) { - ImGui::Text("Commander X16 Zsound Music File"); - if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) { - if (zsmExportTickRate<1) zsmExportTickRate=1; - if (zsmExportTickRate>44100) zsmExportTickRate=44100; - } - ImGui::Checkbox("loop",&zsmExportLoop); - ImGui::SameLine(); - ImGui::Checkbox("optimize size",&zsmExportOptimize); - if (ImGui::Button("Export")) { - openFileDialog(GUI_FILE_EXPORT_ZSM); - ImGui::CloseCurrentPopup(); - } ImGui::EndTabItem(); } - } - int numAmiga=0; - for (int i=0; isong.systemLen; i++) { - if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; - } - if (numAmiga && settings.iCannotWait) { - if (ImGui::BeginTabItem("Amiga Validation")) { + int numZSMCompat=0; + for (int i=0; isong.systemLen; i++) { + if ((e->song.system[i] == DIV_SYSTEM_VERA) || (e->song.system[i] == DIV_SYSTEM_YM2151)) numZSMCompat++; + } + if (numZSMCompat > 0) { + if (ImGui::BeginTabItem("ZSM")) { + ImGui::Text("Commander X16 Zsound Music File"); + if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) { + if (zsmExportTickRate<1) zsmExportTickRate=1; + if (zsmExportTickRate>44100) zsmExportTickRate=44100; + } + ImGui::Checkbox("loop",&zsmExportLoop); + ImGui::SameLine(); + ImGui::Checkbox("optimize size",&zsmExportOptimize); + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_ZSM); + ImGui::CloseCurrentPopup(); + } + ImGui::EndTabItem(); + } + } + int numAmiga=0; + for (int i=0; isong.systemLen; i++) { + if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; + } + if (numAmiga && settings.iCannotWait) { + if (ImGui::BeginTabItem("Amiga Validation")) { + ImGui::Text( + "this is NOT ROM export! only use for making sure the\n" + "Furnace Amiga emulator is working properly by\n" + "comparing it with real Amiga output." + ); + ImGui::AlignTextToFramePadding(); + ImGui::Text("Directory"); + ImGui::SameLine(); + ImGui::InputText("##AVDPath",&workingDirROMExport); + if (ImGui::Button("Bake Data")) { + std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); + if (workingDirROMExport.size()>0) { + if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; + } + for (DivROMExportOutput& i: out) { + String path=workingDirROMExport+i.name; + FILE* outFile=ps_fopen(path.c_str(),"wb"); + if (outFile!=NULL) { + fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); + fclose(outFile); + } + i.data->finish(); + delete i.data; + } + showError(fmt::sprintf("Done! Baked %d files.",(int)out.size())); + ImGui::CloseCurrentPopup(); + } + ImGui::EndTabItem(); + } + } + if (ImGui::BeginTabItem("Text")) { ImGui::Text( - "this is NOT ROM export! only use for making sure the\n" - "Furnace Amiga emulator is working properly by\n" - "comparing it with real Amiga output." + "this option exports the song to a text file.\n" ); - ImGui::AlignTextToFramePadding(); - ImGui::Text("Directory"); - ImGui::SameLine(); - ImGui::InputText("##AVDPath",&workingDirROMExport); - if (ImGui::Button("Bake Data")) { - std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); - if (workingDirROMExport.size()>0) { - if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; - } - for (DivROMExportOutput& i: out) { - String path=workingDirROMExport+i.name; - FILE* outFile=ps_fopen(path.c_str(),"wb"); - if (outFile!=NULL) { - fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); - fclose(outFile); - } - i.data->finish(); - delete i.data; - } - showError(fmt::sprintf("Done! Baked %d files.",(int)out.size())); + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_TEXT); ImGui::CloseCurrentPopup(); } ImGui::EndTabItem(); } - } - if (ImGui::BeginTabItem("Text")) { - ImGui::Text( - "this option exports the song to a text file.\n" - ); - if (ImGui::Button("Export")) { - openFileDialog(GUI_FILE_EXPORT_TEXT); - ImGui::CloseCurrentPopup(); - } - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("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" + if (ImGui::BeginTabItem("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 (binary)")) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); - ImGui::CloseCurrentPopup(); + "technical/development use only!" + ); + if (ImGui::Button("Export (binary)")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Export (text)")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); + ImGui::CloseCurrentPopup(); + } + ImGui::EndTabItem(); } - if (ImGui::Button("Export (text)")) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); - ImGui::CloseCurrentPopup(); - } - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); + ImGui::EndTabBar(); + } + ImGui::EndChild(); } ImGui::Separator(); if (ImGui::Button("Cancel")) ImGui::CloseCurrentPopup(); From 594eb55942398d874e32f689cd8d61e8a259e454 Mon Sep 17 00:00:00 2001 From: Eknous-P Date: Wed, 6 Dec 2023 15:50:19 +0400 Subject: [PATCH 04/53] rename the file --- CMakeLists.txt | 2 +- src/gui/{exportWin.cpp => exportOptions.cpp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/gui/{exportWin.cpp => exportOptions.cpp} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5336f3131..ed571d76c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -779,7 +779,7 @@ src/gui/doAction.cpp src/gui/editing.cpp src/gui/editControls.cpp src/gui/effectList.cpp -src/gui/exportWin.cpp +src/gui/exportOptions.cpp src/gui/findReplace.cpp src/gui/fmPreview.cpp src/gui/gradient.cpp diff --git a/src/gui/exportWin.cpp b/src/gui/exportOptions.cpp similarity index 100% rename from src/gui/exportWin.cpp rename to src/gui/exportOptions.cpp From 96ad124100ea73dffa1086020c78902e9e9c00ea Mon Sep 17 00:00:00 2001 From: Eknous-P Date: Fri, 8 Dec 2023 16:08:31 +0400 Subject: [PATCH 05/53] make a member of FurnaceGUI --- src/gui/exportOptions.cpp | 1 - src/gui/gui.cpp | 4 +++- src/gui/gui.h | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gui/exportOptions.cpp b/src/gui/exportOptions.cpp index ed1cc2f34..ab3323c0f 100644 --- a/src/gui/exportOptions.cpp +++ b/src/gui/exportOptions.cpp @@ -31,7 +31,6 @@ void FurnaceGUI::drawExport() { if (ImGui::BeginChild("sysPickerC",avail,false,ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) { if (ImGui::BeginTabBar("ExportTypes")) { if (ImGui::BeginTabItem("Audio")) { - static int audioExportType=0; ImGui::RadioButton("one file",&audioExportType,0); ImGui::RadioButton("multiple files (one per chip)",&audioExportType,1); ImGui::RadioButton("multiple files (one per channel)",&audioExportType,2); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 99c7297ab..95b506891 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -7688,7 +7688,9 @@ FurnaceGUI::FurnaceGUI(): introSkipDo(false), introStopped(false), curTutorial(-1), - curTutorialStep(0) { + curTutorialStep(0), + //audio export types (export options) + audioExportType(0) { // value keys valueKeys[SDLK_0]=0; valueKeys[SDLK_1]=1; diff --git a/src/gui/gui.h b/src/gui/gui.h index c9af9bd95..9e203ebca 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2267,6 +2267,9 @@ class FurnaceGUI { // tutorial int curTutorial, curTutorialStep; + //audio export types (export options) + int audioExportType; + void drawSSGEnv(unsigned char type, const ImVec2& size); void drawWaveform(unsigned char type, bool opz, const ImVec2& size); void drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size); From d347c85e1b5be1b556ee493c10f4a41c89d771b7 Mon Sep 17 00:00:00 2001 From: Eknous-P Date: Fri, 8 Dec 2023 23:06:56 +0400 Subject: [PATCH 06/53] remove extra indent --- src/gui/exportOptions.cpp | 348 +++++++++++++++++++------------------- 1 file changed, 174 insertions(+), 174 deletions(-) diff --git a/src/gui/exportOptions.cpp b/src/gui/exportOptions.cpp index ab3323c0f..4e3982dec 100644 --- a/src/gui/exportOptions.cpp +++ b/src/gui/exportOptions.cpp @@ -30,203 +30,203 @@ void FurnaceGUI::drawExport() { if (ImGui::BeginChild("sysPickerC",avail,false,ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) { if (ImGui::BeginTabBar("ExportTypes")) { - if (ImGui::BeginTabItem("Audio")) { - ImGui::RadioButton("one file",&audioExportType,0); - ImGui::RadioButton("multiple files (one per chip)",&audioExportType,1); - ImGui::RadioButton("multiple files (one per channel)",&audioExportType,2); - if (ImGui::InputInt("Loops",&exportLoops,1,2)) { - if (exportLoops<0) exportLoops=0; - } - if (ImGui::InputDouble("Fade out (seconds)",&exportFadeOut,1.0,2.0,"%.1f")) { - if (exportFadeOut<0.0) exportFadeOut=0.0; - } + if (ImGui::BeginTabItem("Audio")) { + ImGui::RadioButton("one file",&audioExportType,0); + ImGui::RadioButton("multiple files (one per chip)",&audioExportType,1); + ImGui::RadioButton("multiple files (one per channel)",&audioExportType,2); + if (ImGui::InputInt("Loops",&exportLoops,1,2)) { + if (exportLoops<0) exportLoops=0; + } + if (ImGui::InputDouble("Fade out (seconds)",&exportFadeOut,1.0,2.0,"%.1f")) { + if (exportFadeOut<0.0) exportFadeOut=0.0; + } - if (ImGui::Button("Export")) { - switch (audioExportType) { - case 0: - openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); - break; - case 1: - openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); - break; - case 2: - openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL); - break; - } - ImGui::CloseCurrentPopup(); + if (ImGui::Button("Export")) { + switch (audioExportType) { + case 0: + openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); + break; + case 1: + openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); + break; + case 2: + openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL); + break; } - ImGui::EndTabItem(); + ImGui::CloseCurrentPopup(); } - if (ImGui::BeginTabItem("VGM")) { - ImGui::Text("settings:"); - if (ImGui::BeginCombo("format version",fmt::sprintf("%d.%.2x",vgmExportVersion>>8,vgmExportVersion&0xff).c_str())) { - for (int i=0; i<7; i++) { - if (ImGui::Selectable(fmt::sprintf("%d.%.2x",vgmVersions[i]>>8,vgmVersions[i]&0xff).c_str(),vgmExportVersion==vgmVersions[i])) { - vgmExportVersion=vgmVersions[i]; - } - } - ImGui::EndCombo(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("VGM")) { + ImGui::Text("settings:"); + if (ImGui::BeginCombo("format version",fmt::sprintf("%d.%.2x",vgmExportVersion>>8,vgmExportVersion&0xff).c_str())) { + for (int i=0; i<7; i++) { + if (ImGui::Selectable(fmt::sprintf("%d.%.2x",vgmVersions[i]>>8,vgmVersions[i]&0xff).c_str(),vgmExportVersion==vgmVersions[i])) { + vgmExportVersion=vgmVersions[i]; } - ImGui::Checkbox("loop",&vgmExportLoop); - if (vgmExportLoop && e->song.loopModality==2) { - ImGui::Text("loop trail:"); - ImGui::Indent(); - if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) { - vgmExportTrailingTicks=-1; - } - if (ImGui::RadioButton("add one loop",vgmExportTrailingTicks==-2)) { - vgmExportTrailingTicks=-2; - } - if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) { - vgmExportTrailingTicks=0; - } - if (vgmExportTrailingTicks>=0) { - ImGui::SameLine(); - if (ImGui::InputInt("##TrailTicks",&vgmExportTrailingTicks,1,100)) { - if (vgmExportTrailingTicks<0) vgmExportTrailingTicks=0; - } - } - ImGui::Unindent(); - } - ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip( - "inserts data blocks on pattern changes.\n" - "useful if you are writing a playback routine.\n\n" - - "the format of a pattern change data block is:\n" - "67 66 FE ll ll ll ll 01 oo rr pp pp pp ...\n" - "- ll: length, a 32-bit little-endian number\n" - "- oo: order\n" - "- rr: initial row (a 0Dxx effect is able to select a different row)\n" - "- pp: pattern index (one per channel)\n\n" - - "pattern indexes are ordered as they appear in the song." - ); - } - ImGui::Checkbox("direct stream mode",&vgmExportDirectStream); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip( - "required for DualPCM and MSM6258 export.\n\n" - "allows for volume/direction changes when playing samples,\n" - "at the cost of a massive increase in file size." - ); - } - ImGui::Text("chips to export:"); - bool hasOneAtLeast=false; - for (int i=0; isong.systemLen; i++) { - int minVersion=e->minVGMVersion(e->song.system[i]); - ImGui::BeginDisabled(minVersion>vgmExportVersion || minVersion==0); - ImGui::Checkbox(fmt::sprintf("%d. %s##_SYSV%d",i+1,getSystemName(e->song.system[i]),i).c_str(),&willExport[i]); - ImGui::EndDisabled(); - if (minVersion>vgmExportVersion) { - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetTooltip("this chip is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); - } - } else if (minVersion==0) { - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetTooltip("this chip is not supported by the VGM format!"); - } - } else { - if (willExport[i]) hasOneAtLeast=true; - } - } - ImGui::Text("select the chip you wish to export, but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); - if (hasOneAtLeast) { - if (ImGui::Button("Export")) { - openFileDialog(GUI_FILE_EXPORT_VGM); - ImGui::CloseCurrentPopup(); - } - } else { - ImGui::Text("nothing to export"); - } - ImGui::EndTabItem(); - } - int numZSMCompat=0; - for (int i=0; isong.systemLen; i++) { - if ((e->song.system[i] == DIV_SYSTEM_VERA) || (e->song.system[i] == DIV_SYSTEM_YM2151)) numZSMCompat++; - } - if (numZSMCompat > 0) { - if (ImGui::BeginTabItem("ZSM")) { - ImGui::Text("Commander X16 Zsound Music File"); - if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) { - if (zsmExportTickRate<1) zsmExportTickRate=1; - if (zsmExportTickRate>44100) zsmExportTickRate=44100; } - ImGui::Checkbox("loop",&zsmExportLoop); - ImGui::SameLine(); - ImGui::Checkbox("optimize size",&zsmExportOptimize); - if (ImGui::Button("Export")) { - openFileDialog(GUI_FILE_EXPORT_ZSM); - ImGui::CloseCurrentPopup(); - } - ImGui::EndTabItem(); + ImGui::EndCombo(); } - } - int numAmiga=0; - for (int i=0; isong.systemLen; i++) { - if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; - } - if (numAmiga && settings.iCannotWait) { - if (ImGui::BeginTabItem("Amiga Validation")) { - ImGui::Text( - "this is NOT ROM export! only use for making sure the\n" - "Furnace Amiga emulator is working properly by\n" - "comparing it with real Amiga output." + ImGui::Checkbox("loop",&vgmExportLoop); + if (vgmExportLoop && e->song.loopModality==2) { + ImGui::Text("loop trail:"); + ImGui::Indent(); + if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) { + vgmExportTrailingTicks=-1; + } + if (ImGui::RadioButton("add one loop",vgmExportTrailingTicks==-2)) { + vgmExportTrailingTicks=-2; + } + if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) { + vgmExportTrailingTicks=0; + } + if (vgmExportTrailingTicks>=0) { + ImGui::SameLine(); + if (ImGui::InputInt("##TrailTicks",&vgmExportTrailingTicks,1,100)) { + if (vgmExportTrailingTicks<0) vgmExportTrailingTicks=0; + } + } + ImGui::Unindent(); + } + ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "inserts data blocks on pattern changes.\n" + "useful if you are writing a playback routine.\n\n" + + "the format of a pattern change data block is:\n" + "67 66 FE ll ll ll ll 01 oo rr pp pp pp ...\n" + "- ll: length, a 32-bit little-endian number\n" + "- oo: order\n" + "- rr: initial row (a 0Dxx effect is able to select a different row)\n" + "- pp: pattern index (one per channel)\n\n" + + "pattern indexes are ordered as they appear in the song." ); - ImGui::AlignTextToFramePadding(); - ImGui::Text("Directory"); - ImGui::SameLine(); - ImGui::InputText("##AVDPath",&workingDirROMExport); - if (ImGui::Button("Bake Data")) { - std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); - if (workingDirROMExport.size()>0) { - if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; + } + ImGui::Checkbox("direct stream mode",&vgmExportDirectStream); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "required for DualPCM and MSM6258 export.\n\n" + "allows for volume/direction changes when playing samples,\n" + "at the cost of a massive increase in file size." + ); + } + ImGui::Text("chips to export:"); + bool hasOneAtLeast=false; + for (int i=0; isong.systemLen; i++) { + int minVersion=e->minVGMVersion(e->song.system[i]); + ImGui::BeginDisabled(minVersion>vgmExportVersion || minVersion==0); + ImGui::Checkbox(fmt::sprintf("%d. %s##_SYSV%d",i+1,getSystemName(e->song.system[i]),i).c_str(),&willExport[i]); + ImGui::EndDisabled(); + if (minVersion>vgmExportVersion) { + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + ImGui::SetTooltip("this chip is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); } - for (DivROMExportOutput& i: out) { - String path=workingDirROMExport+i.name; - FILE* outFile=ps_fopen(path.c_str(),"wb"); - if (outFile!=NULL) { - fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); - fclose(outFile); - } - i.data->finish(); - delete i.data; + } else if (minVersion==0) { + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + ImGui::SetTooltip("this chip is not supported by the VGM format!"); } - showError(fmt::sprintf("Done! Baked %d files.",(int)out.size())); + } else { + if (willExport[i]) hasOneAtLeast=true; + } + } + ImGui::Text("select the chip you wish to export, but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); + if (hasOneAtLeast) { + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_VGM); ImGui::CloseCurrentPopup(); } - ImGui::EndTabItem(); + } else { + ImGui::Text("nothing to export"); } - } - if (ImGui::BeginTabItem("Text")) { - ImGui::Text( - "this option exports the song to a text file.\n" - ); + ImGui::EndTabItem(); + } + int numZSMCompat=0; + for (int i=0; isong.systemLen; i++) { + if ((e->song.system[i] == DIV_SYSTEM_VERA) || (e->song.system[i] == DIV_SYSTEM_YM2151)) numZSMCompat++; + } + if (numZSMCompat > 0) { + if (ImGui::BeginTabItem("ZSM")) { + ImGui::Text("Commander X16 Zsound Music File"); + if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) { + if (zsmExportTickRate<1) zsmExportTickRate=1; + if (zsmExportTickRate>44100) zsmExportTickRate=44100; + } + ImGui::Checkbox("loop",&zsmExportLoop); + ImGui::SameLine(); + ImGui::Checkbox("optimize size",&zsmExportOptimize); if (ImGui::Button("Export")) { - openFileDialog(GUI_FILE_EXPORT_TEXT); + openFileDialog(GUI_FILE_EXPORT_ZSM); ImGui::CloseCurrentPopup(); } ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("Command Stream")) { + } + int numAmiga=0; + for (int i=0; isong.systemLen; i++) { + if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; + } + if (numAmiga && settings.iCannotWait) { + if (ImGui::BeginTabItem("Amiga Validation")) { 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!" + "this is NOT ROM export! only use for making sure the\n" + "Furnace Amiga emulator is working properly by\n" + "comparing it with real Amiga output." ); - if (ImGui::Button("Export (binary)")) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); - ImGui::CloseCurrentPopup(); - } - if (ImGui::Button("Export (text)")) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); + ImGui::AlignTextToFramePadding(); + ImGui::Text("Directory"); + ImGui::SameLine(); + ImGui::InputText("##AVDPath",&workingDirROMExport); + if (ImGui::Button("Bake Data")) { + std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); + if (workingDirROMExport.size()>0) { + if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; + } + for (DivROMExportOutput& i: out) { + String path=workingDirROMExport+i.name; + FILE* outFile=ps_fopen(path.c_str(),"wb"); + if (outFile!=NULL) { + fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); + fclose(outFile); + } + i.data->finish(); + delete i.data; + } + showError(fmt::sprintf("Done! Baked %d files.",(int)out.size())); ImGui::CloseCurrentPopup(); } ImGui::EndTabItem(); } + } + if (ImGui::BeginTabItem("Text")) { + ImGui::Text( + "this option exports the song to a text file.\n" + ); + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_TEXT); + ImGui::CloseCurrentPopup(); + } + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("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 (binary)")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Export (text)")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); + ImGui::CloseCurrentPopup(); + } + ImGui::EndTabItem(); + } ImGui::EndTabBar(); } ImGui::EndChild(); From 00c4a44eff42ffbd84c2d88beebdb9a453ad8952 Mon Sep 17 00:00:00 2001 From: Electric Keet Date: Thu, 14 Dec 2023 11:17:37 -0800 Subject: [PATCH 07/53] Docs for N163 chip options. --- doc/7-systems/n163.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/7-systems/n163.md b/doc/7-systems/n163.md index a810f8106..e9f55dc0b 100644 --- a/doc/7-systems/n163.md +++ b/doc/7-systems/n163.md @@ -39,6 +39,15 @@ if the waveform changes (e.g. ins change, wave macro or wave synth), or the **lo - make sure to use `21xx` first! - `21xx`: **set position for 20xx.** +## chip options + +- **Initial channel limit**: sets the number of channels that will be active. + - each additional channel reduces sound quality. + - each additional channel reduces available wavetable memory by 8 bytes, equivalent to a waveform length of 16. +- **Disable hissing**: switches from multiplexing to mixing, which increases output quality but is not accurate to hardware. +- **Scale frequency to wave length**: automatically adjusts note frequency to account for differing waveform lengths. + - if disabled, note frequencies ignore waveveform length. this is FamiTracker's behavior. + ## info this chip uses the [Namco 163](../4-instrument/n163.md) instrument editor. From 3742a8e2ea54a66e146c0f3e9ea7200fff6d6719 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Sun, 17 Dec 2023 17:05:33 +0100 Subject: [PATCH 08/53] Update opll.md formatting --- doc/7-systems/opll.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/7-systems/opll.md b/doc/7-systems/opll.md index 2cf068c13..4de890b22 100644 --- a/doc/7-systems/opll.md +++ b/doc/7-systems/opll.md @@ -15,8 +15,8 @@ the YM2413 is equipped with the following features: - a drum/percussion mode, replacing the last 3 voices with 5 rhythm channels, with drum mode tones hard-defined in the chip itself, like FM instruments. only pitch might be altered. - drum mode works like following: FM channel 7 is for Kick Drum, which is a normal FM channel but routed through mixer twice for 2× volume, like all drum sounds. FM channel 8 splits to Snare, Drum, and Hi-Hat. Snare Drum is the carrier and it works with a special 1 bit noise generator combined with a square wave, all possible by overriding phase-generator with some different synthesis method. Hi-Hat is the modulator and it works with the noise generator and also the special synthesis. channel 9 splits to Top-Cymbal and Tom-Tom, Top-Cymbal is the carrier and only has the special synthesis, while Tom-Tom is basically a 1op wave. - special synthesis mentioned already is: 5 square waves are gathered from 4×, 64× and 128× the pitch of channel 8 and 16× and 64× the pitch of channel 9 and they go through a process where 2 HH bits OR'd together, then 1 HH and 1 TC bit OR'd, then the two TC bits OR'd together, and those 3 results get XOR'd. -- 1 user-definable patch (this patch can be changed throughout the course of the song) -- 15 pre-defined patches which can all be used at the same time +- **1 user-definable patch (this patch can be changed throughout the course of the song)** +- **15 pre-defined patches which can all be used at the same time** - support for ADSR on both the modulator and the carrier - sine and half-sine based FM synthesis - 9 octave note control From 8d30ac4d3baa5b2459540838e72b9c99ac365f0b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Dec 2023 14:41:25 -0500 Subject: [PATCH 09/53] OPN: proper vol map for SSG/ADPCM/CSM/DAC parts --- src/engine/platform/fmshared_OPN.h | 11 +++++++++++ src/engine/platform/genesis.cpp | 6 ++++++ src/engine/platform/genesis.h | 1 + src/engine/platform/genesisext.cpp | 6 ++++++ src/engine/platform/genesisext.h | 1 + src/engine/platform/ym2203ext.cpp | 6 ++++++ src/engine/platform/ym2203ext.h | 1 + src/engine/platform/ym2608ext.cpp | 6 ++++++ src/engine/platform/ym2608ext.h | 1 + src/engine/platform/ym2610bext.cpp | 6 ++++++ src/engine/platform/ym2610bext.h | 1 + src/engine/platform/ym2610ext.cpp | 6 ++++++ src/engine/platform/ym2610ext.h | 1 + 13 files changed, 53 insertions(+) diff --git a/src/engine/platform/fmshared_OPN.h b/src/engine/platform/fmshared_OPN.h index 32ea4c002..3f1267739 100644 --- a/src/engine/platform/fmshared_OPN.h +++ b/src/engine/platform/fmshared_OPN.h @@ -186,6 +186,17 @@ class DivPlatformOPN: public DivPlatformFMBase { void setCombo(bool combo) { useCombo=combo; } + virtual int mapVelocity(int ch, float vel) { + if (ch==csmChan) return vel*127.0; + if (ch==adpcmBChanOffs) return vel*255.0; + if (ch>=adpcmAChanOffs) { + if (vel==0) return 0; + if (vel==127) return 31; + return CLAMP(round(32.0-(56.0-log2(vel*127.0)*8.0)),0,31); + } + if (ch>=psgChanOffs) return round(15.0*pow(vel,0.33)); + return DivPlatformFMBase::mapVelocity(ch,vel); + } }; #endif diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 633bf2886..c90404eae 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -1300,6 +1300,12 @@ DivDispatchOscBuffer* DivPlatformGenesis::getOscBuffer(int ch) { return oscBuf[ch]; } +int DivPlatformGenesis::mapVelocity(int ch, float vel) { + if (ch==csmChan) return DivPlatformOPN::mapVelocity(ch,vel); + if (ch>5) return DivPlatformOPN::mapVelocity(5,vel); + return DivPlatformOPN::mapVelocity(ch,vel); +} + unsigned char* DivPlatformGenesis::getRegisterPool() { return regPool; } diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 82d4301b9..a12e6625d 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -112,6 +112,7 @@ class DivPlatformGenesis: public DivPlatformOPN { virtual unsigned short getPan(int chan); DivSamplePos getSamplePos(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); + virtual int mapVelocity(int ch, float vel); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index aab02e1e1..0097f763f 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -818,6 +818,12 @@ DivDispatchOscBuffer* DivPlatformGenesisExt::getOscBuffer(int ch) { return NULL; } +int DivPlatformGenesisExt::mapVelocity(int ch, float vel) { + if (ch>=extChanOffs+4) return DivPlatformGenesis::mapVelocity(ch-3,vel); + if (ch>=extChanOffs) return DivPlatformGenesis::mapVelocity(extChanOffs,vel); + return DivPlatformGenesis::mapVelocity(ch,vel); +} + void DivPlatformGenesisExt::reset() { DivPlatformGenesis::reset(); diff --git a/src/engine/platform/genesisext.h b/src/engine/platform/genesisext.h index 63112c069..d6ab86924 100644 --- a/src/engine/platform/genesisext.h +++ b/src/engine/platform/genesisext.h @@ -36,6 +36,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis { DivMacroInt* getChanMacroInt(int ch); unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); + int mapVelocity(int ch, float vel); void reset(); void forceIns(); void tick(bool sysTick=true); diff --git a/src/engine/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index adb312ad8..bcb84e19b 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -692,6 +692,12 @@ DivDispatchOscBuffer* DivPlatformYM2203Ext::getOscBuffer(int ch) { return NULL; } +int DivPlatformYM2203Ext::mapVelocity(int ch, float vel) { + if (ch>=extChanOffs+4) return DivPlatformOPN::mapVelocity(ch-3,vel); + if (ch>=extChanOffs) return DivPlatformOPN::mapVelocity(extChanOffs,vel); + return DivPlatformOPN::mapVelocity(ch,vel); +} + void DivPlatformYM2203Ext::reset() { DivPlatformYM2203::reset(); diff --git a/src/engine/platform/ym2203ext.h b/src/engine/platform/ym2203ext.h index 731e2a202..f0e468158 100644 --- a/src/engine/platform/ym2203ext.h +++ b/src/engine/platform/ym2203ext.h @@ -34,6 +34,7 @@ class DivPlatformYM2203Ext: public DivPlatformYM2203 { void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); + int mapVelocity(int ch, float vel); void reset(); void forceIns(); void tick(bool sysTick=true); diff --git a/src/engine/platform/ym2608ext.cpp b/src/engine/platform/ym2608ext.cpp index 50424d4eb..589bd542a 100644 --- a/src/engine/platform/ym2608ext.cpp +++ b/src/engine/platform/ym2608ext.cpp @@ -767,6 +767,12 @@ DivDispatchOscBuffer* DivPlatformYM2608Ext::getOscBuffer(int ch) { return NULL; } +int DivPlatformYM2608Ext::mapVelocity(int ch, float vel) { + if (ch>=extChanOffs+4) return DivPlatformOPN::mapVelocity(ch-3,vel); + if (ch>=extChanOffs) return DivPlatformOPN::mapVelocity(extChanOffs,vel); + return DivPlatformOPN::mapVelocity(ch,vel); +} + void DivPlatformYM2608Ext::reset() { DivPlatformYM2608::reset(); diff --git a/src/engine/platform/ym2608ext.h b/src/engine/platform/ym2608ext.h index 0c9c1a418..159a6c807 100644 --- a/src/engine/platform/ym2608ext.h +++ b/src/engine/platform/ym2608ext.h @@ -35,6 +35,7 @@ class DivPlatformYM2608Ext: public DivPlatformYM2608 { DivMacroInt* getChanMacroInt(int ch); unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); + int mapVelocity(int ch, float vel); void reset(); void forceIns(); void tick(bool sysTick=true); diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 545a80e98..c0b0282b2 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -757,6 +757,12 @@ DivDispatchOscBuffer* DivPlatformYM2610BExt::getOscBuffer(int ch) { return NULL; } +int DivPlatformYM2610BExt::mapVelocity(int ch, float vel) { + if (ch>=extChanOffs+4) return DivPlatformOPN::mapVelocity(ch-3,vel); + if (ch>=extChanOffs) return DivPlatformOPN::mapVelocity(extChanOffs,vel); + return DivPlatformOPN::mapVelocity(ch,vel); +} + void DivPlatformYM2610BExt::reset() { DivPlatformYM2610B::reset(); diff --git a/src/engine/platform/ym2610bext.h b/src/engine/platform/ym2610bext.h index 024119cff..e7feaf3c2 100644 --- a/src/engine/platform/ym2610bext.h +++ b/src/engine/platform/ym2610bext.h @@ -35,6 +35,7 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B { DivMacroInt* getChanMacroInt(int ch); unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); + int mapVelocity(int ch, float vel); void reset(); void forceIns(); void tick(bool sysTick=true); diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 39e341675..063fd5c36 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -757,6 +757,12 @@ DivDispatchOscBuffer* DivPlatformYM2610Ext::getOscBuffer(int ch) { return NULL; } +int DivPlatformYM2610Ext::mapVelocity(int ch, float vel) { + if (ch>=extChanOffs+4) return DivPlatformOPN::mapVelocity(ch-3,vel); + if (ch>=extChanOffs) return DivPlatformOPN::mapVelocity(extChanOffs,vel); + return DivPlatformOPN::mapVelocity(ch,vel); +} + void DivPlatformYM2610Ext::reset() { DivPlatformYM2610::reset(); diff --git a/src/engine/platform/ym2610ext.h b/src/engine/platform/ym2610ext.h index f860a36cd..666f0d40f 100644 --- a/src/engine/platform/ym2610ext.h +++ b/src/engine/platform/ym2610ext.h @@ -35,6 +35,7 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 { DivMacroInt* getChanMacroInt(int ch); unsigned short getPan(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); + int mapVelocity(int ch, float vel); void reset(); void forceIns(); void tick(bool sysTick=true); From 0208883fa1c5be5f92af5566d11002cf65cae3f2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Dec 2023 14:54:38 -0500 Subject: [PATCH 10/53] OPL and OPLL vol map --- src/engine/platform/fmshared_OPN.h | 2 +- src/engine/platform/fmsharedbase.h | 2 +- src/engine/platform/opl.cpp | 15 +++++++++++++++ src/engine/platform/opl.h | 1 + src/engine/platform/opll.cpp | 7 +++++++ src/engine/platform/opll.h | 1 + 6 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/fmshared_OPN.h b/src/engine/platform/fmshared_OPN.h index 3f1267739..e7d06c7f5 100644 --- a/src/engine/platform/fmshared_OPN.h +++ b/src/engine/platform/fmshared_OPN.h @@ -191,7 +191,7 @@ class DivPlatformOPN: public DivPlatformFMBase { if (ch==adpcmBChanOffs) return vel*255.0; if (ch>=adpcmAChanOffs) { if (vel==0) return 0; - if (vel==127) return 31; + if (vel>=1.0) return 31; return CLAMP(round(32.0-(56.0-log2(vel*127.0)*8.0)),0,31); } if (ch>=psgChanOffs) return round(15.0*pow(vel,0.33)); diff --git a/src/engine/platform/fmsharedbase.h b/src/engine/platform/fmsharedbase.h index 3a12f96e6..f20292a0d 100644 --- a/src/engine/platform/fmsharedbase.h +++ b/src/engine/platform/fmsharedbase.h @@ -132,7 +132,7 @@ class DivPlatformFMBase: public DivDispatch { // -36: 2: 48 // -42: 1: 56 if (vel==0) return 0; - if (vel==127) return 127; + if (vel>=1.0) return 127; return CLAMP(round(128.0-(56.0-log2(vel*127.0)*8.0)),0,127); } diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 8eb95fc28..c68271743 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -2104,6 +2104,21 @@ DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) { return oscBuf[ch]; } +int DivPlatformOPL::mapVelocity(int ch, float vel) { + if (ch==adpcmChan) return vel*255.0; + // -0.75dB per step + // -6: 64: 8 + // -12: 32: 16 + // -18: 16: 24 + // -24: 8: 32 + // -30: 4: 40 + // -36: 2: 48 + // -42: 1: 56 + if (vel==0) return 0; + if (vel>=1.0) return 63; + return CLAMP(round(64.0-(56.0-log2(vel*127.0)*8.0)),0,63); +} + unsigned char* DivPlatformOPL::getRegisterPool() { return regPool; } diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index c4346a21e..bef02db5e 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -150,6 +150,7 @@ class DivPlatformOPL: public DivDispatch { unsigned short getPan(int chan); DivChannelPair getPaired(int chan); DivDispatchOscBuffer* getOscBuffer(int chan); + int mapVelocity(int ch, float vel); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 3f1621c02..9228568ef 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -962,6 +962,13 @@ DivDispatchOscBuffer* DivPlatformOPLL::getOscBuffer(int ch) { return oscBuf[ch]; } +int DivPlatformOPLL::mapVelocity(int ch, float vel) { + // -3dB per step + if (vel==0) return 0; + if (vel>=1.0) return 15; + return CLAMP(round(16.0-(14.0-log2(vel*127.0)*2.0)),0,15); +} + unsigned char* DivPlatformOPLL::getRegisterPool() { return regPool; } diff --git a/src/engine/platform/opll.h b/src/engine/platform/opll.h index e8bd627a4..70ece9c1e 100644 --- a/src/engine/platform/opll.h +++ b/src/engine/platform/opll.h @@ -95,6 +95,7 @@ class DivPlatformOPLL: public DivDispatch { void* getChanState(int chan); DivMacroInt* getChanMacroInt(int ch); DivDispatchOscBuffer* getOscBuffer(int chan); + int mapVelocity(int ch, float vel); unsigned char* getRegisterPool(); int getRegisterPoolSize(); void reset(); From 39481ab571992ab074cf88bac48be752abf3be28 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Dec 2023 15:08:52 -0500 Subject: [PATCH 11/53] fix velocity input --- src/engine/engine.cpp | 7 +++++++ src/engine/engine.h | 3 +++ src/gui/gui.cpp | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index f8af13d80..a7ed96807 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2185,6 +2185,13 @@ int DivEngine::getMaxVolumeChan(int ch) { return chan[ch].volMax>>8; } +int DivEngine::mapVelocity(int ch, float vel) { + if (ch<0) return 0; + if (ch>=chans) return 0; + if (disCont[dispatchOfChan[ch]].dispatch==NULL) return 0; + return disCont[dispatchOfChan[ch]].dispatch->mapVelocity(dispatchChanOfChan[ch],vel); +} + unsigned char DivEngine::getOrder() { return prevOrder; } diff --git a/src/engine/engine.h b/src/engine/engine.h index fe0cd0991..c3cd0ff72 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -851,6 +851,9 @@ class DivEngine { // get channel max volume int getMaxVolumeChan(int chan); + // map MIDI velocity to volume + int mapVelocity(int ch, float vel); + // get current order unsigned char getOrder(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index a9efea15b..1683c434d 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1216,7 +1216,7 @@ void FurnaceGUI::noteInput(int num, int key, int vol) { if (latchVol!=-1) { pat->data[cursor.y][3]=MIN(maxVol,latchVol); } else if (vol!=-1) { - pat->data[cursor.y][3]=(vol*maxVol)/127; + pat->data[cursor.y][3]=e->mapVelocity(cursor.xCoarse,pow((float)vol/127.0f,midiMap.volExp)); } if (latchEffect!=-1) pat->data[cursor.y][4]=latchEffect; if (latchEffectVal!=-1) pat->data[cursor.y][5]=latchEffectVal; @@ -3776,7 +3776,7 @@ bool FurnaceGUI::loop() { noteInput( msg.data[0]-12, 0, - midiMap.volInput?((int)(pow((double)msg.data[1]/127.0,midiMap.volExp)*127.0)):-1 + midiMap.volInput?msg.data[1]:-1 ); } } else { From 478f7bb3bd9afbd1ad435b8b0304b314edd66462 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 17 Dec 2023 15:30:51 -0500 Subject: [PATCH 12/53] MIDI input: program change pass-through option --- src/engine/engine.cpp | 4 ++++ src/engine/engine.h | 17 +++++++++++------ src/engine/playback.cpp | 19 ++++++++++++++----- src/gui/gui.cpp | 3 ++- src/gui/gui.h | 3 ++- src/gui/midiMap.cpp | 2 ++ src/gui/settings.cpp | 12 +++++++++++- 7 files changed, 46 insertions(+), 14 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a7ed96807..b2150ec1b 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3392,6 +3392,10 @@ void DivEngine::setMidiDirect(bool value) { midiIsDirect=value; } +void DivEngine::setMidiDirectProgram(bool value) { + midiIsDirectProgram=value; +} + void DivEngine::setMidiVolExp(float value) { midiVolExp=value; } diff --git a/src/engine/engine.h b/src/engine/engine.h index c3cd0ff72..4170119e9 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -176,16 +176,16 @@ struct DivNoteEvent { signed char channel; unsigned char ins; signed char note, volume; - bool on, nop, pad1, pad2; - DivNoteEvent(int c, int i, int n, int v, bool o): + bool on, nop, insChange, fromMIDI; + DivNoteEvent(int c, int i, int n, int v, bool o, bool ic=false, bool fm=false): channel(c), ins(i), note(n), volume(v), on(o), nop(false), - pad1(false), - pad2(false) {} + insChange(ic), + fromMIDI(fm) {} DivNoteEvent(): channel(-1), ins(0), @@ -193,8 +193,8 @@ struct DivNoteEvent { volume(-1), on(false), nop(true), - pad1(false), - pad2(false) {} + insChange(false), + fromMIDI(false) {} }; struct DivDispatchContainer { @@ -415,6 +415,7 @@ class DivEngine { bool firstTick; bool skipping; bool midiIsDirect; + bool midiIsDirectProgram; bool lowLatency; bool systemsRegistered; bool hasLoadedSomething; @@ -1188,6 +1189,9 @@ class DivEngine { // set MIDI direct channel map void setMidiDirect(bool value); + // set MIDI direct program change + void setMidiDirectProgram(bool value); + // set MIDI volume curve exponent void setMidiVolExp(float value); @@ -1260,6 +1264,7 @@ class DivEngine { firstTick(false), skipping(false), midiIsDirect(false), + midiIsDirectProgram(false), lowLatency(false), systemsRegistered(false), hasLoadedSomething(false), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 59c93cf8a..2ab9ead8e 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1370,8 +1370,15 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { pendingNotes.pop_front(); continue; } + if (note.insChange) { + dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,note.channel,note.ins,0)); + pendingNotes.pop_front(); + continue; + } if (note.on) { - dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,note.channel,note.ins,1)); + if (!(midiIsDirect && midiIsDirectProgram && note.fromMIDI)) { + dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,note.channel,note.ins,1)); + } if (note.volume>=0 && !disCont[dispatchOfChan[note.channel]].dispatch->isVolGlobal()) { float curvedVol=pow((float)note.volume/127.0f,midiVolExp); int mappedVol=disCont[dispatchOfChan[note.channel]].dispatch->mapVelocity(dispatchChanOfChan[note.channel],curvedVol); @@ -1832,7 +1839,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi case TA_MIDI_NOTE_OFF: { if (chan<0 || chan>=chans) break; if (midiIsDirect) { - pendingNotes.push_back(DivNoteEvent(chan,-1,-1,-1,false)); + pendingNotes.push_back(DivNoteEvent(chan,-1,-1,-1,false,false,true)); } else { autoNoteOff(msg.type&15,msg.data[0]-12,msg.data[1]); } @@ -1847,13 +1854,13 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi if (chan<0 || chan>=chans) break; if (msg.data[1]==0) { if (midiIsDirect) { - pendingNotes.push_back(DivNoteEvent(chan,-1,-1,-1,false)); + pendingNotes.push_back(DivNoteEvent(chan,-1,-1,-1,false,false,true)); } else { autoNoteOff(msg.type&15,msg.data[0]-12,msg.data[1]); } } else { if (midiIsDirect) { - pendingNotes.push_back(DivNoteEvent(chan,ins,msg.data[0]-12,msg.data[1],true)); + pendingNotes.push_back(DivNoteEvent(chan,ins,msg.data[0]-12,msg.data[1],true,false,true)); } else { autoNoteOn(msg.type&15,ins,msg.data[0]-12,msg.data[1]); } @@ -1861,7 +1868,9 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi break; } case TA_MIDI_PROGRAM: { - // TODO: change instrument event thingy + if (midiIsDirect && midiIsDirectProgram) { + pendingNotes.push_back(DivNoteEvent(chan,msg.data[0],0,0,false,true,true)); + } break; } } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 1683c434d..74d615f52 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3803,7 +3803,7 @@ bool FurnaceGUI::loop() { } break; case TA_MIDI_PROGRAM: - if (midiMap.programChange) { + if (midiMap.programChange && !(midiMap.directChannel && midiMap.directProgram)) { curIns=msg.data[0]; if (curIns>=(int)e->song.ins.size()) curIns=e->song.ins.size()-1; wavePreviewInit=true; @@ -7018,6 +7018,7 @@ bool FurnaceGUI::init() { return -2; } + if (midiMap.directChannel && midiMap.directProgram) return -1; return curIns; }); diff --git a/src/gui/gui.h b/src/gui/gui.h index f754a073d..57251fe6d 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -936,7 +936,7 @@ struct MIDIMap { int**** map; std::vector binds; - bool noteInput, volInput, rawVolume, polyInput, directChannel, programChange, midiClock, midiTimeCode, yamahaFMResponse; + bool noteInput, volInput, rawVolume, polyInput, directChannel, programChange, midiClock, midiTimeCode, yamahaFMResponse, directProgram; // 0: disabled // // 1: C- C# D- D# E- F- F# G- G# A- A# B- @@ -999,6 +999,7 @@ struct MIDIMap { midiClock(false), midiTimeCode(false), yamahaFMResponse(false), + directProgram(false), valueInputStyle(1), valueInputControlMSB(0), valueInputControlLSB(0), diff --git a/src/gui/midiMap.cpp b/src/gui/midiMap.cpp index 602347107..fac0399b8 100644 --- a/src/gui/midiMap.cpp +++ b/src/gui/midiMap.cpp @@ -139,6 +139,7 @@ bool MIDIMap::read(String path) { UNDERSTAND_OPTION(midiClock) else UNDERSTAND_OPTION(midiTimeCode) else UNDERSTAND_OPTION(yamahaFMResponse) else + UNDERSTAND_OPTION(directProgram) else UNDERSTAND_OPTION(valueInputStyle) else UNDERSTAND_OPTION(valueInputControlMSB) else UNDERSTAND_OPTION(valueInputControlLSB) else @@ -205,6 +206,7 @@ bool MIDIMap::write(String path) { WRITE_OPTION(midiClock); WRITE_OPTION(midiTimeCode); WRITE_OPTION(yamahaFMResponse); + WRITE_OPTION(directProgram); WRITE_OPTION(valueInputStyle); WRITE_OPTION(valueInputControlMSB); WRITE_OPTION(valueInputControlLSB); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index dbf1a9661..585f49b18 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1141,10 +1141,19 @@ void FurnaceGUI::drawSettings() { //ImGui::Checkbox("Polyphonic/chord input",&midiMap.polyInput); if (ImGui::Checkbox("Map MIDI channels to direct channels",&midiMap.directChannel)) { e->setMidiDirect(midiMap.directChannel); + e->setMidiDirectProgram(midiMap.directChannel && midiMap.directProgram); settingsChanged=true; } + if (midiMap.directChannel) { + if (ImGui::Checkbox("Program change pass-through",&midiMap.directProgram)) { + e->setMidiDirectProgram(midiMap.directChannel && midiMap.directProgram); + settingsChanged=true; + } + } if (ImGui::Checkbox("Map Yamaha FM voice data to instruments",&midiMap.yamahaFMResponse)) settingsChanged=true; - if (ImGui::Checkbox("Program change is instrument selection",&midiMap.programChange)) settingsChanged=true; + if (!(midiMap.directChannel && midiMap.directProgram)) { + if (ImGui::Checkbox("Program change is instrument selection",&midiMap.programChange)) settingsChanged=true; + } //ImGui::Checkbox("Listen to MIDI clock",&midiMap.midiClock); //ImGui::Checkbox("Listen to MIDI time code",&midiMap.midiTimeCode); if (ImGui::Combo("Value input style",&midiMap.valueInputStyle,valueInputStyles,7)) settingsChanged=true; @@ -4029,6 +4038,7 @@ void FurnaceGUI::syncSettings() { midiMap.compile(); e->setMidiDirect(midiMap.directChannel); + e->setMidiDirectProgram(midiMap.directChannel && midiMap.directProgram); e->setMidiVolExp(midiMap.volExp); e->setMetronomeVol(((float)settings.metroVol)/100.0f); e->setSamplePreviewVol(((float)settings.sampleVol)/100.0f); From 8ded0eb6736b34cde5cc998932f18c187cfce6ae Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 18 Dec 2023 10:32:44 -0500 Subject: [PATCH 13/53] GUI: possibly fix crash when loading ESFM ins --- src/gui/dataList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 527848a63..b45739dcb 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -76,7 +76,7 @@ void FurnaceGUI::insListItem(int i, int dir, int asset) { const char* insType="Bug!"; if (i>=0 && isong.insLen) { DivInstrument* ins=e->song.ins[i]; - insType=(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type][0]; + insType=(ins->type>=DIV_INS_MAX)?"Unknown":insTypes[ins->type][0]; const char** insIcon=NULL; if (ins->type>=DIV_INS_MAX) { From 56f020e77b0921c7d9474b5c4f0bdbd123871dfe Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 18 Dec 2023 10:39:51 -0500 Subject: [PATCH 14/53] fix the N163 doc --- doc/7-systems/n163.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/doc/7-systems/n163.md b/doc/7-systems/n163.md index e9f55dc0b..78cc2562c 100644 --- a/doc/7-systems/n163.md +++ b/doc/7-systems/n163.md @@ -6,7 +6,7 @@ it has 256 nibbles (128 bytes) of internal RAM which is shared between channel s wavetables are variable in size and may be allocated anywhere in RAM. at least 128 nibbles (64 bytes) can be dedicated to waves, with more available if not all channels are used - waveform RAM area becomes smaller as more channels are activated, since channel registers consume 8 bytes for each channel. -Namco 163 uses time-division multiplexing for its output. this means that only one channel is output per sample (like OPLL and OPN2). therefore, its sound quality gets worse as more channels are activated. +Namco 163 uses time-division multiplexing (TDM) for its output. this means that only one channel is output per sample (like OPLL and OPN2). therefore, its sound quality gets worse as more channels are activated. ## waveform load position versus waveform position @@ -41,12 +41,10 @@ if the waveform changes (e.g. ins change, wave macro or wave synth), or the **lo ## chip options -- **Initial channel limit**: sets the number of channels that will be active. - - each additional channel reduces sound quality. - - each additional channel reduces available wavetable memory by 8 bytes, equivalent to a waveform length of 16. -- **Disable hissing**: switches from multiplexing to mixing, which increases output quality but is not accurate to hardware. +- **Initial channel limit**: sets the number of channels that will be active. higher values reduce volume and make TDM artifacts more noticeable. +- **Disable hissing**: remove TDM artifacts by mixing. sacrifices some accuracy! - **Scale frequency to wave length**: automatically adjusts note frequency to account for differing waveform lengths. - - if disabled, note frequencies ignore waveveform length. this is FamiTracker's behavior. + - if disabled, note frequencies ignore waveveform length. this is how FamiTracker behaves. ## info From 75323be54ea3d2c9437fd75a35d99a7f50a71de4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 18 Dec 2023 10:56:45 -0500 Subject: [PATCH 15/53] GUI: do not set ins type if it is unknown --- src/gui/insEdit.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 3e2eb578d..130c70d5d 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2885,14 +2885,8 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("Type"); ImGui::TableNextColumn(); - if (ins->type>=DIV_INS_MAX) ins->type=DIV_INS_FM; int insType=ins->type; ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - /* - if (ImGui::Combo("##Type",&insType,insTypes,DIV_INS_MAX,DIV_INS_MAX)) { - ins->type=(DivInstrumentType)insType; - } - */ bool warnType=true; for (DivInstrumentType i: e->getPossibleInsTypes()) { if (i==insType) { @@ -2901,7 +2895,7 @@ void FurnaceGUI::drawInsEdit() { } pushWarningColor(warnType,warnType && failedNoteOn); - if (ImGui::BeginCombo("##Type",insTypes[insType][0])) { + if (ImGui::BeginCombo("##Type",(insType>=DIV_INS_MAX)?"Unknown":insTypes[insType][0])) { std::vector insTypeList; if (settings.displayAllInsTypes) { for (int i=0; insTypes[i][0]; i++) { @@ -6521,6 +6515,12 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_VRC6) { insTabSample(ins); } + if (ins->type>=DIV_INS_MAX) { + if (ImGui::BeginTabItem("Error")) { + ImGui::Text("invalid instrument type! change it first."); + ImGui::EndTabItem(); + } + } ImGui::EndTabBar(); } if (settings.insEditColorize) { From 4f862199892af96d3e4a5aff936bf7519df2d75d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 18 Dec 2023 11:01:11 -0500 Subject: [PATCH 16/53] GUI: fix possible crash with colorize ins --- src/gui/insEdit.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 130c70d5d..f6fff0b3c 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2824,7 +2824,11 @@ void FurnaceGUI::drawInsEdit() { updateFMPreview=false; } if (settings.insEditColorize) { - pushAccentColors(uiColors[GUI_COLOR_INSTR_STD+ins->type],uiColors[GUI_COLOR_INSTR_STD+ins->type],uiColors[GUI_COLOR_INSTR_STD+ins->type],ImVec4(0.0f,0.0f,0.0f,0.0f)); + if (ins->type>=DIV_INS_MAX) { + pushAccentColors(uiColors[GUI_COLOR_INSTR_UNKNOWN],uiColors[GUI_COLOR_INSTR_UNKNOWN],uiColors[GUI_COLOR_INSTR_UNKNOWN],ImVec4(0.0f,0.0f,0.0f,0.0f)); + } else { + pushAccentColors(uiColors[GUI_COLOR_INSTR_STD+ins->type],uiColors[GUI_COLOR_INSTR_STD+ins->type],uiColors[GUI_COLOR_INSTR_STD+ins->type],ImVec4(0.0f,0.0f,0.0f,0.0f)); + } } if (ImGui::BeginTable("InsProp",3)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); From b7d525b4bd9495fec56a77e0cf790d747bc36388 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 18 Dec 2023 11:06:45 -0500 Subject: [PATCH 17/53] GUI: walk song on load issue #1541 --- src/gui/gui.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 74d615f52..8b4a49dfd 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2246,6 +2246,8 @@ int FurnaceGUI::load(String path) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } pushRecentFile(path); + // walk song + e->walkSong(loopOrder,loopRow,loopEnd); // do not auto-play a backup if (path.find(backupPath)!=0) { if (settings.playOnLoad==2 || (settings.playOnLoad==1 && wasPlaying)) { From 0fd9131088be6beb33ef0aa2ab229920c70030d4 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:23:01 +0100 Subject: [PATCH 18/53] fix pc-9801-86 preset --- src/gui/presets.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index e64452721..bb9432cf7 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -742,11 +742,11 @@ void FurnaceGUI::initSystemPresets() { ENTRY( "NEC PC-98 (with PC-9801-86)", { // -73 also has OPNA CH(DIV_SYSTEM_YM2608, 1.0f, 0, "clockSel=1"), - CH(DIV_SYSTEM_PCM_DAC, 1.0f, 0, // 2x 16-bit Burr Brown DAC + CH(DIV_SYSTEM_PCM_DAC, 1.0f, -1, // 2x 16-bit Burr Brown DAC "rate=44100\n" "outDepth=15\n" ), - CH(DIV_SYSTEM_PCM_DAC, 1.0f, 0, + CH(DIV_SYSTEM_PCM_DAC, 1.0f, 1, "rate=44100\n" "outDepth=15\n" ), From 1fb3b95a78e35d04996c63f658bddb2734246d4e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 18 Dec 2023 15:21:51 -0500 Subject: [PATCH 19/53] document new MIDI options --- doc/2-interface/settings.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/2-interface/settings.md b/doc/2-interface/settings.md index 3571e7dd6..94ba24c4a 100644 --- a/doc/2-interface/settings.md +++ b/doc/2-interface/settings.md @@ -123,11 +123,14 @@ settings are saved when clicking the **OK** or **Apply** buttons at the bottom o - **Note input**: enables note input. disable if you intend to use this device only for binding actions. - **Velocity input**: enables velocity input when entering notes in the pattern. - **Map MIDI channels to direct channels**: when enabled, notes from MIDI channels will be mapped to channels rather than the cursor position. +- **Program change pass-through**: when enabled, program change events are sent to each channel as instrument change commands. + - this option is only available when the previous one is enabled. - **Map Yamaha FM voice data to instruments**: when enabled, Furnace will listen for any transmitted Yamaha SysEx patches. - this option is only useful if you have a Yamaha FM synthesizer (e.g. TX81Z). - selecting a voice or using the "Voice Transmit?" option will send a patch, and Furnace will create a new instrument with its data. - this may also be triggered by clicking on "Receive from TX81Z" in the instrument editor (OPZ only). - **Program change is instrument selection**: changes the current instrument when a program change event is received. + - this option is not available when "Program change pass-through" is enabled. - **Value input style**: changes the way values are entered when the pattern cursor is not in the Note column. the following styles are available: - **Disabled/custom**: no value input through MIDI. - **Two octaves (0 is C-4, F is D#5)**: maps keys in two octaves to single nibble input. the layout is: @@ -149,6 +152,7 @@ settings are saved when clicking the **OK** or **Apply** buttons at the bottom o - **Control**: select the CC number that will change the value. - **Per-column control change**: when enabled, you can map several control change events to a channel's columns. - **Volume curve**: adjust the velocity to volume curve. + - the default is 2.0, which matches General MIDI standard. - **Actions**: this allows you to bind note input and control change events to actions. - **`+`** button: adds a new action. - window-with-arrow button: new action with learning! press a button or move a slider/knob/something on your device. From 6fbf64c753b30299319bde3fcd336994599b58a4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 18 Dec 2023 15:27:56 -0500 Subject: [PATCH 20/53] Revert "fix pc-9801-86 preset" This reverts commit 0fd9131088be6beb33ef0aa2ab229920c70030d4. --- src/gui/presets.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index bb9432cf7..e64452721 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -742,11 +742,11 @@ void FurnaceGUI::initSystemPresets() { ENTRY( "NEC PC-98 (with PC-9801-86)", { // -73 also has OPNA CH(DIV_SYSTEM_YM2608, 1.0f, 0, "clockSel=1"), - CH(DIV_SYSTEM_PCM_DAC, 1.0f, -1, // 2x 16-bit Burr Brown DAC + CH(DIV_SYSTEM_PCM_DAC, 1.0f, 0, // 2x 16-bit Burr Brown DAC "rate=44100\n" "outDepth=15\n" ), - CH(DIV_SYSTEM_PCM_DAC, 1.0f, 1, + CH(DIV_SYSTEM_PCM_DAC, 1.0f, 0, "rate=44100\n" "outDepth=15\n" ), From 14f29ca6a5a2a95f9bbfee2deebaa8351793fa91 Mon Sep 17 00:00:00 2001 From: Electric Keet Date: Tue, 19 Dec 2023 01:50:59 -0800 Subject: [PATCH 21/53] Revise MIDI "Value input style" docs. (#1653) * Fix MIDI "Value input style" docs. The individual backtick-quoted lines didn't work great, as they ignored the first space in each line. Triple-backticks do the trick. Or perhaps I should replace these with graphics? * Revert "Fix MIDI "Value input style" docs." This reverts commit 76b731c424f8fb4f837332c56110686e849d75e5. * Replacing text with images. * Removing labels. --- doc/2-interface/MIDI-value-input-1.png | Bin 0 -> 40172 bytes doc/2-interface/MIDI-value-input-2.png | Bin 0 -> 40122 bytes doc/2-interface/settings.md | 8 ++------ 3 files changed, 2 insertions(+), 6 deletions(-) create mode 100644 doc/2-interface/MIDI-value-input-1.png create mode 100644 doc/2-interface/MIDI-value-input-2.png diff --git a/doc/2-interface/MIDI-value-input-1.png b/doc/2-interface/MIDI-value-input-1.png new file mode 100644 index 0000000000000000000000000000000000000000..4e5df7be8d49d9fc41067d17e2b3cc36ef6a1c7e GIT binary patch literal 40172 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 literal 0 HcmV?d00001 diff --git a/doc/2-interface/MIDI-value-input-2.png b/doc/2-interface/MIDI-value-input-2.png new file mode 100644 index 0000000000000000000000000000000000000000..5173a2ded279143733bf2559ccf382b4727b35af GIT binary patch literal 40122 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 AxBvhE literal 0 HcmV?d00001 diff --git a/doc/2-interface/settings.md b/doc/2-interface/settings.md index 94ba24c4a..b3a93b04c 100644 --- a/doc/2-interface/settings.md +++ b/doc/2-interface/settings.md @@ -134,14 +134,10 @@ settings are saved when clicking the **OK** or **Apply** buttons at the bottom o - **Value input style**: changes the way values are entered when the pattern cursor is not in the Note column. the following styles are available: - **Disabled/custom**: no value input through MIDI. - **Two octaves (0 is C-4, F is D#5)**: maps keys in two octaves to single nibble input. the layout is: - - ` - octave n -- octave n+1 -` - - ` 1 3 6 8 A D F # # # ` - - `0 2 4 5 7 9 B C E # # # # #` + ![two octaves layout 1](MIDI-value-input-1.png) - **Raw (note number is value)**: the note number becomes the input value. not useful if you want to input anything above 7F. - **Two octaves alternate (lower keys are 0-9, upper keys are A-F)**: maps keys in two octaves, but with a different layout: - - ` - octave n -- octave n+1 -` - - ` A B C D E F # # # # ` - - `0 1 2 3 4 5 6 7 8 9 # # # #` + ![two octaves layout 2](MIDI-value-input-2.png) - **Use dual control change (one for each nibble)**: maps two control change events to the nibbles of a value. - **CC of upper nibble**: select the CC number that will change the upper nibble. - **CC of lower nibble**: select the CC number that will change the lower nibble. From 37195e57591946fc242202746b133526cce7327d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 19 Dec 2023 04:55:30 -0500 Subject: [PATCH 22/53] GUI: fix macros tab being visible in unknown ins --- src/gui/insEdit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index f6fff0b3c..6a0f204d6 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -5967,7 +5967,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTabItem(); } } - if (ImGui::BeginTabItem("Macros")) { + if (ins->type Date: Wed, 20 Dec 2023 19:51:21 -0500 Subject: [PATCH 23/53] dev190 - GUI: color scheme guru mode now you can fine-tune every color in the interface TODO: improve color config format --- src/engine/engine.h | 4 +- src/gui/gui.h | 33 ++++++++ src/gui/guiConst.cpp | 31 +++++++ src/gui/settings.cpp | 194 ++++++++++++++++++++++++++++++++----------- 4 files changed, 210 insertions(+), 52 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index 4170119e9..7773183c9 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -54,8 +54,8 @@ class DivWorkPool; #define DIV_UNSTABLE -#define DIV_VERSION "dev189" -#define DIV_ENGINE_VERSION 189 +#define DIV_VERSION "dev190" +#define DIV_ENGINE_VERSION 190 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 diff --git a/src/gui/gui.h b/src/gui/gui.h index 57251fe6d..e591dd818 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -101,6 +101,7 @@ enum FurnaceGUIColors { GUI_COLOR_MODAL_BACKDROP, GUI_COLOR_HEADER, GUI_COLOR_TEXT, + GUI_COLOR_TEXT_DISABLED, GUI_COLOR_ACCENT_PRIMARY, GUI_COLOR_ACCENT_SECONDARY, GUI_COLOR_TITLE_INACTIVE, @@ -124,6 +125,36 @@ enum FurnaceGUIColors { GUI_COLOR_NAV_HIGHLIGHT, GUI_COLOR_NAV_WIN_HIGHLIGHT, GUI_COLOR_NAV_WIN_BACKDROP, + GUI_COLOR_PLOT_LINES, + GUI_COLOR_PLOT_LINES_HOVER, + GUI_COLOR_PLOT_HISTOGRAM, + GUI_COLOR_PLOT_HISTOGRAM_HOVER, + + GUI_COLOR_BUTTON, + GUI_COLOR_BUTTON_HOVER, + GUI_COLOR_BUTTON_ACTIVE, + GUI_COLOR_TAB, + GUI_COLOR_TAB_HOVER, + GUI_COLOR_TAB_ACTIVE, + GUI_COLOR_TAB_UNFOCUSED, + GUI_COLOR_TAB_UNFOCUSED_ACTIVE, + GUI_COLOR_IMGUI_HEADER, + GUI_COLOR_IMGUI_HEADER_HOVER, + GUI_COLOR_IMGUI_HEADER_ACTIVE, + GUI_COLOR_RESIZE_GRIP, + GUI_COLOR_RESIZE_GRIP_HOVER, + GUI_COLOR_RESIZE_GRIP_ACTIVE, + GUI_COLOR_WIDGET_BACKGROUND, + GUI_COLOR_WIDGET_BACKGROUND_HOVER, + GUI_COLOR_WIDGET_BACKGROUND_ACTIVE, + GUI_COLOR_SLIDER_GRAB, + GUI_COLOR_SLIDER_GRAB_ACTIVE, + GUI_COLOR_TITLE_BACKGROUND_ACTIVE, + GUI_COLOR_CHECK_MARK, + GUI_COLOR_TEXT_SELECTION, + GUI_COLOR_TABLE_ROW_EVEN, + GUI_COLOR_TABLE_ROW_ODD, + GUI_COLOR_TOGGLE_OFF, GUI_COLOR_TOGGLE_ON, GUI_COLOR_EDITING, @@ -1655,6 +1686,7 @@ class FurnaceGUI { int fontAutoHint; int fontAntiAlias; int selectAssetOnLoad; + int basicColors; unsigned int maxUndoSteps; String mainFontPath; String headFontPath; @@ -1850,6 +1882,7 @@ class FurnaceGUI { fontAutoHint(1), fontAntiAlias(1), selectAssetOnLoad(1), + basicColors(1), maxUndoSteps(100), mainFontPath(""), headFontPath(""), diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 9761af578..0966ecf11 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -794,6 +794,7 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_MODAL_BACKDROP,"",ImVec4(0.0f,0.0f,0.0f,0.55f)), D(GUI_COLOR_HEADER,"",ImVec4(0.2f,0.2f,0.2f,1.0f)), D(GUI_COLOR_TEXT,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_TEXT_DISABLED,"",ImVec4(0.5f,0.5f,0.5f,1.0f)), D(GUI_COLOR_ACCENT_PRIMARY,"",ImVec4(0.06f,0.53f,0.98f,1.0f)), D(GUI_COLOR_ACCENT_SECONDARY,"",ImVec4(0.26f,0.59f,0.98f,1.0f)), D(GUI_COLOR_TITLE_INACTIVE,"",ImVec4(0.04f,0.04f,0.04f,1.0f)), @@ -817,6 +818,36 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_NAV_HIGHLIGHT,"",ImVec4(0.26f,0.59f,0.98f,1.0f)), D(GUI_COLOR_NAV_WIN_HIGHLIGHT,"",ImVec4(1.0f,1.0f,1.0f,0.7f)), D(GUI_COLOR_NAV_WIN_BACKDROP,"",ImVec4(0.8f,0.8f,0.8f,0.2f)), + D(GUI_COLOR_PLOT_LINES,"",ImVec4(0.61f,0.61f,0.61f,1.0f)), + D(GUI_COLOR_PLOT_LINES_HOVER,"",ImVec4(1.00f,0.43f,0.35f,1.00f)), + D(GUI_COLOR_PLOT_HISTOGRAM,"",ImVec4(0.0f,0.9f,1.0f,1.0f)), + D(GUI_COLOR_PLOT_HISTOGRAM_HOVER,"",ImVec4(0.0f,0.9f,1.0f,1.0f)), + + D(GUI_COLOR_BUTTON,"",ImVec4(0.085f,0.216f,0.343f,1.0f)), + D(GUI_COLOR_BUTTON_HOVER,"",ImVec4(0.075f,0.287f,0.49f,1.0f)), + D(GUI_COLOR_BUTTON_ACTIVE,"",ImVec4(0.06f,0.53f,0.98f,1.0f)), + D(GUI_COLOR_TAB,"",ImVec4(0.085f,0.216f,0.343f,1.0f)), + D(GUI_COLOR_TAB_HOVER,"",ImVec4(0.165f,0.313f,0.49f,1.0f)), + D(GUI_COLOR_TAB_ACTIVE,"",ImVec4(0.25f,0.47f,0.735f,1.0f)), + D(GUI_COLOR_TAB_UNFOCUSED,"",ImVec4(0.085f,0.216f,0.343f,1.0f)), + D(GUI_COLOR_TAB_UNFOCUSED_ACTIVE,"",ImVec4(0.075f,0.287f,0.49f,1.0f)), + D(GUI_COLOR_IMGUI_HEADER,"",ImVec4(0.083f,0.156f,0.245f,1.0f)), + D(GUI_COLOR_IMGUI_HEADER_HOVER,"",ImVec4(0.165f,0.313f,0.49f,1.0f)), + D(GUI_COLOR_IMGUI_HEADER_ACTIVE,"",ImVec4(0.26f,0.59f,0.98f,1.0f)), + D(GUI_COLOR_RESIZE_GRIP,"",ImVec4(0.083f,0.156f,0.245f,1.0f)), + D(GUI_COLOR_RESIZE_GRIP_HOVER,"",ImVec4(0.165f,0.313f,0.49f,1.0f)), + D(GUI_COLOR_RESIZE_GRIP_ACTIVE,"",ImVec4(0.26f,0.59f,0.98f,1.0f)), + D(GUI_COLOR_WIDGET_BACKGROUND,"",ImVec4(0.083f,0.156f,0.245f,1.0f)), + D(GUI_COLOR_WIDGET_BACKGROUND_HOVER,"",ImVec4(0.165f,0.313f,0.49f,1.0f)), + D(GUI_COLOR_WIDGET_BACKGROUND_ACTIVE,"",ImVec4(0.26f,0.59f,0.98f,1.0f)), + D(GUI_COLOR_SLIDER_GRAB,"",ImVec4(0.06f,0.53f,0.98f,1.0f)), + D(GUI_COLOR_SLIDER_GRAB_ACTIVE,"",ImVec4(0.06f,0.53f,0.98f,1.0f)), + D(GUI_COLOR_TITLE_BACKGROUND_ACTIVE,"",ImVec4(0.085f,0.216f,0.343f,1.0f)), + D(GUI_COLOR_CHECK_MARK,"",ImVec4(0.06f,0.53f,0.98f,1.0f)), + D(GUI_COLOR_TEXT_SELECTION,"",ImVec4(0.165f,0.313f,0.49f,1.0f)), + D(GUI_COLOR_TABLE_ROW_EVEN,"",ImVec4(0.0f,0.0f,0.0f,0.0f)), + D(GUI_COLOR_TABLE_ROW_ODD,"",ImVec4(1.0f,1.0f,1.0f,0.06f)), + D(GUI_COLOR_TOGGLE_OFF,"",ImVec4(0.2f,0.2f,0.2f,1.0f)), D(GUI_COLOR_TOGGLE_ON,"",ImVec4(0.2f,0.6f,0.2f,1.0f)), D(GUI_COLOR_EDITING,"",ImVec4(0.2f,0.1f,0.1f,1.0f)), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 585f49b18..c06e69c00 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -3170,26 +3170,84 @@ void FurnaceGUI::drawSettings() { if (ImGui::Button("Reset defaults")) { showWarning("Are you sure you want to reset the color scheme?",GUI_WARN_RESET_COLORS); } - if (ImGui::TreeNode("General")) { - ImGui::Text("Color scheme type:"); - ImGui::Indent(); - if (ImGui::RadioButton("Dark##gcb0",settings.guiColorsBase==0)) { - settings.guiColorsBase=0; - applyUISettings(false); - settingsChanged=true; + bool basicColorsB=!settings.basicColors; + if (ImGui::Checkbox("Guru mode",&basicColorsB)) { + settings.basicColors=!basicColorsB; + applyUISettings(false); + settingsChanged=true; + } + if (settings.basicColors) { + if (ImGui::TreeNode("Interface")) { + if (ImGui::SliderInt("Frame shading",&settings.guiColorsShading,0,100,"%d%%")) { + if (settings.guiColorsShading<0) settings.guiColorsShading=0; + if (settings.guiColorsShading>100) settings.guiColorsShading=100; + applyUISettings(false); + settingsChanged=true; + } + ImGui::Text("Color scheme type:"); + ImGui::Indent(); + if (ImGui::RadioButton("Dark##gcb0",settings.guiColorsBase==0)) { + settings.guiColorsBase=0; + applyUISettings(false); + settingsChanged=true; + } + if (ImGui::RadioButton("Light##gcb1",settings.guiColorsBase==1)) { + settings.guiColorsBase=1; + applyUISettings(false); + settingsChanged=true; + } + ImGui::Unindent(); + + ImGui::Text("Accent colors:"); + ImGui::Indent(); + UI_COLOR_CONFIG(GUI_COLOR_ACCENT_PRIMARY,"Primary"); + UI_COLOR_CONFIG(GUI_COLOR_ACCENT_SECONDARY,"Secondary"); + ImGui::Unindent(); + + ImGui::TreePop(); } - if (ImGui::RadioButton("Light##gcb1",settings.guiColorsBase==1)) { - settings.guiColorsBase=1; - applyUISettings(false); - settingsChanged=true; - } - ImGui::Unindent(); - if (ImGui::SliderInt("Frame shading",&settings.guiColorsShading,0,100,"%d%%")) { - if (settings.guiColorsShading<0) settings.guiColorsShading=0; - if (settings.guiColorsShading>100) settings.guiColorsShading=100; - applyUISettings(false); - settingsChanged=true; + } else { + if (ImGui::TreeNode("Interface")) { + if (ImGui::SliderInt("Frame shading",&settings.guiColorsShading,0,100,"%d%%")) { + if (settings.guiColorsShading<0) settings.guiColorsShading=0; + if (settings.guiColorsShading>100) settings.guiColorsShading=100; + applyUISettings(false); + settingsChanged=true; + } + + UI_COLOR_CONFIG(GUI_COLOR_BUTTON,"Button"); + UI_COLOR_CONFIG(GUI_COLOR_BUTTON_HOVER,"Button (hovered)"); + UI_COLOR_CONFIG(GUI_COLOR_BUTTON_ACTIVE,"Button (active)"); + UI_COLOR_CONFIG(GUI_COLOR_TAB,"Tab"); + UI_COLOR_CONFIG(GUI_COLOR_TAB_HOVER,"Tab (hovered)"); + UI_COLOR_CONFIG(GUI_COLOR_TAB_ACTIVE,"Tab (active)"); + UI_COLOR_CONFIG(GUI_COLOR_TAB_UNFOCUSED,"Tab (unfocused)"); + UI_COLOR_CONFIG(GUI_COLOR_TAB_UNFOCUSED_ACTIVE,"Tab (unfocused and active)"); + UI_COLOR_CONFIG(GUI_COLOR_IMGUI_HEADER,"ImGui header"); + UI_COLOR_CONFIG(GUI_COLOR_IMGUI_HEADER_HOVER,"ImGui header (hovered)"); + UI_COLOR_CONFIG(GUI_COLOR_IMGUI_HEADER_ACTIVE,"ImGui header (active)"); + UI_COLOR_CONFIG(GUI_COLOR_RESIZE_GRIP,"Resize grip"); + UI_COLOR_CONFIG(GUI_COLOR_RESIZE_GRIP_HOVER,"Resize grip (hovered)"); + UI_COLOR_CONFIG(GUI_COLOR_RESIZE_GRIP_ACTIVE,"Resize grip (active)"); + UI_COLOR_CONFIG(GUI_COLOR_WIDGET_BACKGROUND,"Widget background"); + UI_COLOR_CONFIG(GUI_COLOR_WIDGET_BACKGROUND_HOVER,"Widget background (hovered)"); + UI_COLOR_CONFIG(GUI_COLOR_WIDGET_BACKGROUND_ACTIVE,"Widget background (active)"); + UI_COLOR_CONFIG(GUI_COLOR_SLIDER_GRAB,"Slider grab"); + UI_COLOR_CONFIG(GUI_COLOR_SLIDER_GRAB_ACTIVE,"Slider grab (active)"); + UI_COLOR_CONFIG(GUI_COLOR_TITLE_BACKGROUND_ACTIVE,"Title background (active)"); + UI_COLOR_CONFIG(GUI_COLOR_CHECK_MARK,"Checkbox/radio button mark"); + UI_COLOR_CONFIG(GUI_COLOR_TEXT_SELECTION,"Text selection"); + UI_COLOR_CONFIG(GUI_COLOR_PLOT_LINES,"Line plot"); + UI_COLOR_CONFIG(GUI_COLOR_PLOT_LINES_HOVER,"Line plot (hovered)"); + UI_COLOR_CONFIG(GUI_COLOR_PLOT_HISTOGRAM,"Histogram plot"); + UI_COLOR_CONFIG(GUI_COLOR_PLOT_HISTOGRAM_HOVER,"Histogram plot (hovered)"); + UI_COLOR_CONFIG(GUI_COLOR_TABLE_ROW_EVEN,"Table row (even)"); + UI_COLOR_CONFIG(GUI_COLOR_TABLE_ROW_ODD,"Table row (odd)"); + + ImGui::TreePop(); } + } + if (ImGui::TreeNode("Interface (other)")) { UI_COLOR_CONFIG(GUI_COLOR_BACKGROUND,"Background"); UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND,"Window background"); UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND_CHILD,"Sub-window background"); @@ -3197,8 +3255,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_MODAL_BACKDROP,"Modal backdrop"); UI_COLOR_CONFIG(GUI_COLOR_HEADER,"Header"); UI_COLOR_CONFIG(GUI_COLOR_TEXT,"Text"); - UI_COLOR_CONFIG(GUI_COLOR_ACCENT_PRIMARY,"Primary"); - UI_COLOR_CONFIG(GUI_COLOR_ACCENT_SECONDARY,"Secondary"); + UI_COLOR_CONFIG(GUI_COLOR_TEXT_DISABLED,"Text (disabled)"); UI_COLOR_CONFIG(GUI_COLOR_TITLE_INACTIVE,"Title bar (inactive)"); UI_COLOR_CONFIG(GUI_COLOR_TITLE_COLLAPSED,"Title bar (collapsed)"); UI_COLOR_CONFIG(GUI_COLOR_MENU_BAR,"Menu bar"); @@ -3219,11 +3276,11 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_DRAG_DROP_TARGET,"Drag and drop target"); UI_COLOR_CONFIG(GUI_COLOR_NAV_WIN_HIGHLIGHT,"Window switcher (highlight)"); UI_COLOR_CONFIG(GUI_COLOR_NAV_WIN_BACKDROP,"Window switcher backdrop"); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Miscellaneous")) { UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_ON,"Toggle on"); UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_OFF,"Toggle off"); - UI_COLOR_CONFIG(GUI_COLOR_EDITING,"Editing"); - UI_COLOR_CONFIG(GUI_COLOR_EDITING_CLONE,"Editing (will clone)"); - UI_COLOR_CONFIG(GUI_COLOR_SONG_LOOP,"Song loop"); UI_COLOR_CONFIG(GUI_COLOR_PLAYBACK_STAT,"Playback status"); UI_COLOR_CONFIG(GUI_COLOR_DESTRUCTIVE,"Destructive hint"); UI_COLOR_CONFIG(GUI_COLOR_WARNING,"Warning hint"); @@ -3284,6 +3341,7 @@ void FurnaceGUI::drawSettings() { if (ImGui::TreeNode("Orders")) { UI_COLOR_CONFIG(GUI_COLOR_ORDER_ROW_INDEX,"Order number"); UI_COLOR_CONFIG(GUI_COLOR_ORDER_ACTIVE,"Playing order background"); + UI_COLOR_CONFIG(GUI_COLOR_SONG_LOOP,"Song loop"); UI_COLOR_CONFIG(GUI_COLOR_ORDER_SELECTED,"Selected order"); UI_COLOR_CONFIG(GUI_COLOR_ORDER_SIMILAR,"Similar patterns"); UI_COLOR_CONFIG(GUI_COLOR_ORDER_INACTIVE,"Inactive patterns"); @@ -3396,6 +3454,8 @@ void FurnaceGUI::drawSettings() { } if (ImGui::TreeNode("Pattern")) { UI_COLOR_CONFIG(GUI_COLOR_PATTERN_PLAY_HEAD,"Playhead"); + UI_COLOR_CONFIG(GUI_COLOR_EDITING,"Editing"); + UI_COLOR_CONFIG(GUI_COLOR_EDITING_CLONE,"Editing (will clone)"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_CURSOR,"Cursor"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_CURSOR_HOVER,"Cursor (hovered)"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_CURSOR_ACTIVE,"Cursor (clicked)"); @@ -3819,6 +3879,7 @@ void FurnaceGUI::syncSettings() { settings.fontAutoHint=e->getConfInt("fontAutoHint",1); settings.fontAntiAlias=e->getConfInt("fontAntiAlias",1); settings.selectAssetOnLoad=e->getConfInt("selectAssetOnLoad",1); + settings.basicColors=e->getConfInt("basicColors",1); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.headFontSize,2,96); @@ -3985,6 +4046,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.fontAutoHint,0,2); clampSetting(settings.fontAntiAlias,0,1); clampSetting(settings.selectAssetOnLoad,0,1); + clampSetting(settings.basicColors,0,1); if (settings.exportLoops<0.0) settings.exportLoops=0.0; if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0; @@ -4268,6 +4330,7 @@ void FurnaceGUI::commitSettings() { e->setConf("fontAutoHint",settings.fontAutoHint); e->setConf("fontAntiAlias",settings.fontAntiAlias); e->setConf("selectAssetOnLoad",settings.selectAssetOnLoad); + e->setConf("basicColors",settings.basicColors); // colors for (int i=0; i Date: Thu, 21 Dec 2023 18:14:28 -0500 Subject: [PATCH 24/53] prepare for color scheme import/export chamges this includes a small refactor of the settings mechanism --- src/engine/configEngine.cpp | 6 +- src/engine/engine.h | 3 + src/gui/gui.h | 17 + src/gui/settings.cpp | 845 ++++++++++++++++++------------------ 4 files changed, 451 insertions(+), 420 deletions(-) diff --git a/src/engine/configEngine.cpp b/src/engine/configEngine.cpp index 123a19a81..f226d1364 100644 --- a/src/engine/configEngine.cpp +++ b/src/engine/configEngine.cpp @@ -167,4 +167,8 @@ void DivEngine::setConf(String key, String value) { bool DivEngine::hasConf(String key) { return conf.has(key); -} \ No newline at end of file +} + +DivConfig& DivEngine::getConfObject() { + return conf; +} diff --git a/src/engine/engine.h b/src/engine/engine.h index 7773183c9..eb4bc5bbb 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -702,6 +702,9 @@ class DivEngine { double getConfDouble(String key, double fallback); String getConfString(String key, String fallback); + // get config object + DivConfig& getConfObject(); + // set a config value void setConf(String key, bool value); void setConf(String key, int value); diff --git a/src/gui/gui.h b/src/gui/gui.h index e591dd818..34a9c87bc 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -468,6 +468,20 @@ enum FurnaceGUIMobileScenes { GUI_SCENE_OTHER, }; +enum FurnaceGUISettingGroups: unsigned int { + GUI_SETTINGS_GENERAL=1, + GUI_SETTINGS_AUDIO=2, + GUI_SETTINGS_MIDI=4, + GUI_SETTINGS_KEYBOARD=8, + GUI_SETTINGS_BEHAVIOR=16, + GUI_SETTINGS_FONT=32, + GUI_SETTINGS_APPEARANCE=64, + GUI_SETTINGS_LAYOUTS=128, + GUI_SETTINGS_COLOR=256, + + GUI_SETTINGS_ALL=0xffffffff +}; + enum FurnaceGUIFileDialogs { GUI_FILE_OPEN, GUI_FILE_OPEN_BACKUP, @@ -2453,6 +2467,9 @@ class FurnaceGUI { void resetColors(); void resetKeybinds(); + void readConfig(DivConfig& conf, FurnaceGUISettingGroups groups=GUI_SETTINGS_ALL); + void writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups=GUI_SETTINGS_ALL); + void syncSettings(); void commitSettings(); void syncTutorial(); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index c06e69c00..941580739 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -3678,208 +3678,208 @@ void FurnaceGUI::drawSettings() { x=maxV; \ } -void FurnaceGUI::syncSettings() { - settings.mainFontSize=e->getConfInt("mainFontSize",18); - settings.headFontSize=e->getConfInt("headFontSize",27); - settings.patFontSize=e->getConfInt("patFontSize",18); - settings.iconSize=e->getConfInt("iconSize",16); - settings.audioEngine=(e->getConfString("audioEngine","SDL")=="SDL")?1:0; - if (e->getConfString("audioEngine","SDL")=="JACK") { +void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { + settings.mainFontSize=conf.getInt("mainFontSize",18); + settings.headFontSize=conf.getInt("headFontSize",27); + settings.patFontSize=conf.getInt("patFontSize",18); + settings.iconSize=conf.getInt("iconSize",16); + settings.audioEngine=(conf.getString("audioEngine","SDL")=="SDL")?1:0; + if (conf.getString("audioEngine","SDL")=="JACK") { settings.audioEngine=DIV_AUDIO_JACK; - } else if (e->getConfString("audioEngine","SDL")=="PortAudio") { + } else if (conf.getString("audioEngine","SDL")=="PortAudio") { settings.audioEngine=DIV_AUDIO_PORTAUDIO; } else { settings.audioEngine=DIV_AUDIO_SDL; } - settings.audioDevice=e->getConfString("audioDevice",""); - settings.audioChans=e->getConfInt("audioChans",2); - settings.midiInDevice=e->getConfString("midiInDevice",""); - settings.midiOutDevice=e->getConfString("midiOutDevice",""); - settings.renderDriver=e->getConfString("renderDriver",""); - settings.sdlAudioDriver=e->getConfString("sdlAudioDriver",""); - settings.audioQuality=e->getConfInt("audioQuality",0); - settings.audioHiPass=e->getConfInt("audioHiPass",1); - settings.audioBufSize=e->getConfInt("audioBufSize",1024); - settings.audioRate=e->getConfInt("audioRate",44100); - settings.arcadeCore=e->getConfInt("arcadeCore",0); - settings.ym2612Core=e->getConfInt("ym2612Core",0); - settings.snCore=e->getConfInt("snCore",0); - settings.nesCore=e->getConfInt("nesCore",0); - settings.fdsCore=e->getConfInt("fdsCore",0); - settings.c64Core=e->getConfInt("c64Core",0); - settings.pokeyCore=e->getConfInt("pokeyCore",1); - settings.opnCore=e->getConfInt("opnCore",1); - settings.opl2Core=e->getConfInt("opl2Core",0); - settings.opl3Core=e->getConfInt("opl3Core",0); - settings.arcadeCoreRender=e->getConfInt("arcadeCoreRender",1); - settings.ym2612CoreRender=e->getConfInt("ym2612CoreRender",0); - settings.snCoreRender=e->getConfInt("snCoreRender",0); - settings.nesCoreRender=e->getConfInt("nesCoreRender",0); - settings.fdsCoreRender=e->getConfInt("fdsCoreRender",1); - settings.c64CoreRender=e->getConfInt("c64CoreRender",1); - settings.pokeyCoreRender=e->getConfInt("pokeyCoreRender",1); - settings.opnCoreRender=e->getConfInt("opnCoreRender",1); - settings.opl2CoreRender=e->getConfInt("opl2CoreRender",0); - settings.opl3CoreRender=e->getConfInt("opl3CoreRender",0); - settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0); - settings.yrw801Path=e->getConfString("yrw801Path",""); - settings.tg100Path=e->getConfString("tg100Path",""); - settings.mu5Path=e->getConfString("mu5Path",""); - settings.mainFont=e->getConfInt("mainFont",0); - settings.headFont=e->getConfInt("headFont",0); - settings.patFont=e->getConfInt("patFont",0); - settings.mainFontPath=e->getConfString("mainFontPath",""); - settings.headFontPath=e->getConfString("headFontPath",""); - settings.patFontPath=e->getConfString("patFontPath",""); - settings.patRowsBase=e->getConfInt("patRowsBase",0); - settings.orderRowsBase=e->getConfInt("orderRowsBase",1); - settings.soloAction=e->getConfInt("soloAction",0); - settings.pullDeleteBehavior=e->getConfInt("pullDeleteBehavior",1); - settings.wrapHorizontal=e->getConfInt("wrapHorizontal",0); - settings.wrapVertical=e->getConfInt("wrapVertical",0); - settings.macroView=e->getConfInt("macroView",0); - settings.fmNames=e->getConfInt("fmNames",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); - settings.flatNotes=e->getConfInt("flatNotes",0); - settings.germanNotation=e->getConfInt("germanNotation",0); - settings.stepOnDelete=e->getConfInt("stepOnDelete",0); - settings.scrollStep=e->getConfInt("scrollStep",0); - settings.sysSeparators=e->getConfInt("sysSeparators",1); - settings.forceMono=e->getConfInt("forceMono",0); - settings.controlLayout=e->getConfInt("controlLayout",3); - settings.statusDisplay=e->getConfInt("statusDisplay",0); - settings.dpiScale=e->getConfFloat("dpiScale",0.0f); - settings.viewPrevPattern=e->getConfInt("viewPrevPattern",1); - settings.guiColorsBase=e->getConfInt("guiColorsBase",0); - settings.guiColorsShading=e->getConfInt("guiColorsShading",0); - settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0); - settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1); - settings.stepOnInsert=e->getConfInt("stepOnInsert",0); - settings.unifiedDataView=e->getConfInt("unifiedDataView",0); - settings.sysFileDialog=e->getConfInt("sysFileDialog",SYS_FILE_DIALOG_DEFAULT); - settings.roundedWindows=e->getConfInt("roundedWindows",1); - settings.roundedButtons=e->getConfInt("roundedButtons",1); - settings.roundedMenus=e->getConfInt("roundedMenus",0); - settings.loadJapanese=e->getConfInt("loadJapanese",0); - settings.loadChinese=e->getConfInt("loadChinese",0); - settings.loadChineseTraditional=e->getConfInt("loadChineseTraditional",0); - settings.loadKorean=e->getConfInt("loadKorean",0); - settings.fmLayout=e->getConfInt("fmLayout",4); - settings.sampleLayout=e->getConfInt("sampleLayout",0); - settings.waveLayout=e->getConfInt("waveLayout",0); - settings.susPosition=e->getConfInt("susPosition",0); - settings.effectCursorDir=e->getConfInt("effectCursorDir",1); - settings.cursorPastePos=e->getConfInt("cursorPastePos",1); - settings.titleBarInfo=e->getConfInt("titleBarInfo",1); - settings.titleBarSys=e->getConfInt("titleBarSys",1); - settings.frameBorders=e->getConfInt("frameBorders",0); - settings.effectDeletionAltersValue=e->getConfInt("effectDeletionAltersValue",1); - settings.oscRoundedCorners=e->getConfInt("oscRoundedCorners",1); - settings.oscTakesEntireWindow=e->getConfInt("oscTakesEntireWindow",0); - settings.oscBorder=e->getConfInt("oscBorder",1); - settings.oscEscapesBoundary=e->getConfInt("oscEscapesBoundary",0); - settings.oscMono=e->getConfInt("oscMono",1); - settings.oscAntiAlias=e->getConfInt("oscAntiAlias",1); - settings.separateFMColors=e->getConfInt("separateFMColors",0); - settings.insEditColorize=e->getConfInt("insEditColorize",0); - settings.metroVol=e->getConfInt("metroVol",100); - settings.sampleVol=e->getConfInt("sampleVol",50); - settings.pushNibble=e->getConfInt("pushNibble",0); - settings.scrollChangesOrder=e->getConfInt("scrollChangesOrder",0); - settings.oplStandardWaveNames=e->getConfInt("oplStandardWaveNames",0); - settings.cursorMoveNoScroll=e->getConfInt("cursorMoveNoScroll",0); - settings.lowLatency=e->getConfInt("lowLatency",0); - settings.notePreviewBehavior=e->getConfInt("notePreviewBehavior",1); - settings.powerSave=e->getConfInt("powerSave",POWER_SAVE_DEFAULT); - settings.absorbInsInput=e->getConfInt("absorbInsInput",0); - settings.eventDelay=e->getConfInt("eventDelay",0); - settings.moveWindowTitle=e->getConfInt("moveWindowTitle",1); - settings.hiddenSystems=e->getConfInt("hiddenSystems",0); - settings.horizontalDataView=e->getConfInt("horizontalDataView",0); - settings.noMultiSystem=e->getConfInt("noMultiSystem",0); - settings.oldMacroVSlider=e->getConfInt("oldMacroVSlider",0); - settings.displayAllInsTypes=e->getConfInt("displayAllInsTypes",0); - settings.displayPartial=e->getConfInt("displayPartial",0); - settings.noteCellSpacing=e->getConfInt("noteCellSpacing",0); - settings.insCellSpacing=e->getConfInt("insCellSpacing",0); - settings.volCellSpacing=e->getConfInt("volCellSpacing",0); - settings.effectCellSpacing=e->getConfInt("effectCellSpacing",0); - settings.effectValCellSpacing=e->getConfInt("effectValCellSpacing",0); - settings.doubleClickColumn=e->getConfInt("doubleClickColumn",1); - settings.blankIns=e->getConfInt("blankIns",0); - settings.dragMovesSelection=e->getConfInt("dragMovesSelection",2); - settings.unsignedDetune=e->getConfInt("unsignedDetune",0); - settings.noThreadedInput=e->getConfInt("noThreadedInput",0); - settings.saveWindowPos=e->getConfInt("saveWindowPos",1); - settings.initialSysName=e->getConfString("initialSysName",""); - settings.clampSamples=e->getConfInt("clampSamples",0); - settings.noteOffLabel=e->getConfString("noteOffLabel","OFF"); - settings.noteRelLabel=e->getConfString("noteRelLabel","==="); - settings.macroRelLabel=e->getConfString("macroRelLabel","REL"); - settings.emptyLabel=e->getConfString("emptyLabel","..."); - settings.emptyLabel2=e->getConfString("emptyLabel2",".."); - settings.saveUnusedPatterns=e->getConfInt("saveUnusedPatterns",0); - settings.channelColors=e->getConfInt("channelColors",1); - settings.channelTextColors=e->getConfInt("channelTextColors",0); - settings.channelStyle=e->getConfInt("channelStyle",1); - settings.channelVolStyle=e->getConfInt("channelVolStyle",0); - settings.channelFeedbackStyle=e->getConfInt("channelFeedbackStyle",1); - settings.channelFont=e->getConfInt("channelFont",1); - settings.channelTextCenter=e->getConfInt("channelTextCenter",1); - settings.maxRecentFile=e->getConfInt("maxRecentFile",10); - settings.midiOutClock=e->getConfInt("midiOutClock",0); - settings.midiOutTime=e->getConfInt("midiOutTime",0); - settings.midiOutProgramChange=e->getConfInt("midiOutProgramChange",0); - settings.midiOutMode=e->getConfInt("midiOutMode",1); - settings.midiOutTimeRate=e->getConfInt("midiOutTimeRate",0); - 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); - settings.macroLayout=e->getConfInt("macroLayout",0); - settings.doubleClickTime=e->getConfFloat("doubleClickTime",0.3f); - settings.oneDigitEffects=e->getConfInt("oneDigitEffects",0); - settings.disableFadeIn=e->getConfInt("disableFadeIn",0); - settings.alwaysPlayIntro=e->getConfInt("alwaysPlayIntro",0); - settings.cursorFollowsOrder=e->getConfInt("cursorFollowsOrder",1); - settings.iCannotWait=e->getConfInt("iCannotWait",0); - settings.orderButtonPos=e->getConfInt("orderButtonPos",2); - settings.compress=e->getConfInt("compress",1); - settings.newPatternFormat=e->getConfInt("newPatternFormat",1); - settings.renderBackend=e->getConfString("renderBackend",GUI_BACKEND_DEFAULT_NAME); - settings.renderClearPos=e->getConfInt("renderClearPos",0); - settings.insertBehavior=e->getConfInt("insertBehavior",1); - settings.pullDeleteRow=e->getConfInt("pullDeleteRow",1); - settings.newSongBehavior=e->getConfInt("newSongBehavior",0); - settings.memUsageUnit=e->getConfInt("memUsageUnit",1); - settings.cursorFollowsWheel=e->getConfInt("cursorFollowsWheel",0); - settings.noDMFCompat=e->getConfInt("noDMFCompat",0); - settings.removeInsOff=e->getConfInt("removeInsOff",0); - settings.removeVolOff=e->getConfInt("removeVolOff",0); - settings.playOnLoad=e->getConfInt("playOnLoad",0); - settings.insTypeMenu=e->getConfInt("insTypeMenu",1); - settings.capitalMenuBar=e->getConfInt("capitalMenuBar",0); - settings.centerPopup=e->getConfInt("centerPopup",1); - settings.insIconsStyle=e->getConfInt("insIconsStyle",1); - settings.classicChipOptions=e->getConfInt("classicChipOptions",0); - settings.wasapiEx=e->getConfInt("wasapiEx",0); - settings.chanOscThreads=e->getConfInt("chanOscThreads",0); - settings.renderPoolThreads=e->getConfInt("renderPoolThreads",0); - settings.showPool=e->getConfInt("showPool",0); - settings.writeInsNames=e->getConfInt("writeInsNames",1); - settings.readInsNames=e->getConfInt("readInsNames",1); - settings.defaultAuthorName=e->getConfString("defaultAuthorName",""); - settings.fontBackend=e->getConfInt("fontBackend",FONT_BACKEND_DEFAULT); - settings.fontHinting=e->getConfInt("fontHinting",0); - settings.fontBitmap=e->getConfInt("fontBitmap",0); - settings.fontAutoHint=e->getConfInt("fontAutoHint",1); - settings.fontAntiAlias=e->getConfInt("fontAntiAlias",1); - settings.selectAssetOnLoad=e->getConfInt("selectAssetOnLoad",1); - settings.basicColors=e->getConfInt("basicColors",1); + settings.audioDevice=conf.getString("audioDevice",""); + settings.audioChans=conf.getInt("audioChans",2); + settings.midiInDevice=conf.getString("midiInDevice",""); + settings.midiOutDevice=conf.getString("midiOutDevice",""); + settings.renderDriver=conf.getString("renderDriver",""); + settings.sdlAudioDriver=conf.getString("sdlAudioDriver",""); + settings.audioQuality=conf.getInt("audioQuality",0); + settings.audioHiPass=conf.getInt("audioHiPass",1); + settings.audioBufSize=conf.getInt("audioBufSize",1024); + settings.audioRate=conf.getInt("audioRate",44100); + settings.arcadeCore=conf.getInt("arcadeCore",0); + settings.ym2612Core=conf.getInt("ym2612Core",0); + settings.snCore=conf.getInt("snCore",0); + settings.nesCore=conf.getInt("nesCore",0); + settings.fdsCore=conf.getInt("fdsCore",0); + settings.c64Core=conf.getInt("c64Core",0); + settings.pokeyCore=conf.getInt("pokeyCore",1); + settings.opnCore=conf.getInt("opnCore",1); + settings.opl2Core=conf.getInt("opl2Core",0); + settings.opl3Core=conf.getInt("opl3Core",0); + settings.arcadeCoreRender=conf.getInt("arcadeCoreRender",1); + settings.ym2612CoreRender=conf.getInt("ym2612CoreRender",0); + settings.snCoreRender=conf.getInt("snCoreRender",0); + settings.nesCoreRender=conf.getInt("nesCoreRender",0); + settings.fdsCoreRender=conf.getInt("fdsCoreRender",1); + settings.c64CoreRender=conf.getInt("c64CoreRender",1); + settings.pokeyCoreRender=conf.getInt("pokeyCoreRender",1); + settings.opnCoreRender=conf.getInt("opnCoreRender",1); + settings.opl2CoreRender=conf.getInt("opl2CoreRender",0); + settings.opl3CoreRender=conf.getInt("opl3CoreRender",0); + settings.pcSpeakerOutMethod=conf.getInt("pcSpeakerOutMethod",0); + settings.yrw801Path=conf.getString("yrw801Path",""); + settings.tg100Path=conf.getString("tg100Path",""); + settings.mu5Path=conf.getString("mu5Path",""); + settings.mainFont=conf.getInt("mainFont",0); + settings.headFont=conf.getInt("headFont",0); + settings.patFont=conf.getInt("patFont",0); + settings.mainFontPath=conf.getString("mainFontPath",""); + settings.headFontPath=conf.getString("headFontPath",""); + settings.patFontPath=conf.getString("patFontPath",""); + settings.patRowsBase=conf.getInt("patRowsBase",0); + settings.orderRowsBase=conf.getInt("orderRowsBase",1); + settings.soloAction=conf.getInt("soloAction",0); + settings.pullDeleteBehavior=conf.getInt("pullDeleteBehavior",1); + settings.wrapHorizontal=conf.getInt("wrapHorizontal",0); + settings.wrapVertical=conf.getInt("wrapVertical",0); + settings.macroView=conf.getInt("macroView",0); + settings.fmNames=conf.getInt("fmNames",0); + settings.allowEditDocking=conf.getInt("allowEditDocking",1); + settings.chipNames=conf.getInt("chipNames",0); + settings.overflowHighlight=conf.getInt("overflowHighlight",0); + settings.partyTime=conf.getInt("partyTime",0); + settings.flatNotes=conf.getInt("flatNotes",0); + settings.germanNotation=conf.getInt("germanNotation",0); + settings.stepOnDelete=conf.getInt("stepOnDelete",0); + settings.scrollStep=conf.getInt("scrollStep",0); + settings.sysSeparators=conf.getInt("sysSeparators",1); + settings.forceMono=conf.getInt("forceMono",0); + settings.controlLayout=conf.getInt("controlLayout",3); + settings.statusDisplay=conf.getInt("statusDisplay",0); + settings.dpiScale=conf.getFloat("dpiScale",0.0f); + settings.viewPrevPattern=conf.getInt("viewPrevPattern",1); + settings.guiColorsBase=conf.getInt("guiColorsBase",0); + settings.guiColorsShading=conf.getInt("guiColorsShading",0); + settings.avoidRaisingPattern=conf.getInt("avoidRaisingPattern",0); + settings.insFocusesPattern=conf.getInt("insFocusesPattern",1); + settings.stepOnInsert=conf.getInt("stepOnInsert",0); + settings.unifiedDataView=conf.getInt("unifiedDataView",0); + settings.sysFileDialog=conf.getInt("sysFileDialog",SYS_FILE_DIALOG_DEFAULT); + settings.roundedWindows=conf.getInt("roundedWindows",1); + settings.roundedButtons=conf.getInt("roundedButtons",1); + settings.roundedMenus=conf.getInt("roundedMenus",0); + settings.loadJapanese=conf.getInt("loadJapanese",0); + settings.loadChinese=conf.getInt("loadChinese",0); + settings.loadChineseTraditional=conf.getInt("loadChineseTraditional",0); + settings.loadKorean=conf.getInt("loadKorean",0); + settings.fmLayout=conf.getInt("fmLayout",4); + settings.sampleLayout=conf.getInt("sampleLayout",0); + settings.waveLayout=conf.getInt("waveLayout",0); + settings.susPosition=conf.getInt("susPosition",0); + settings.effectCursorDir=conf.getInt("effectCursorDir",1); + settings.cursorPastePos=conf.getInt("cursorPastePos",1); + settings.titleBarInfo=conf.getInt("titleBarInfo",1); + settings.titleBarSys=conf.getInt("titleBarSys",1); + settings.frameBorders=conf.getInt("frameBorders",0); + settings.effectDeletionAltersValue=conf.getInt("effectDeletionAltersValue",1); + settings.oscRoundedCorners=conf.getInt("oscRoundedCorners",1); + settings.oscTakesEntireWindow=conf.getInt("oscTakesEntireWindow",0); + settings.oscBorder=conf.getInt("oscBorder",1); + settings.oscEscapesBoundary=conf.getInt("oscEscapesBoundary",0); + settings.oscMono=conf.getInt("oscMono",1); + settings.oscAntiAlias=conf.getInt("oscAntiAlias",1); + settings.separateFMColors=conf.getInt("separateFMColors",0); + settings.insEditColorize=conf.getInt("insEditColorize",0); + settings.metroVol=conf.getInt("metroVol",100); + settings.sampleVol=conf.getInt("sampleVol",50); + settings.pushNibble=conf.getInt("pushNibble",0); + settings.scrollChangesOrder=conf.getInt("scrollChangesOrder",0); + settings.oplStandardWaveNames=conf.getInt("oplStandardWaveNames",0); + settings.cursorMoveNoScroll=conf.getInt("cursorMoveNoScroll",0); + settings.lowLatency=conf.getInt("lowLatency",0); + settings.notePreviewBehavior=conf.getInt("notePreviewBehavior",1); + settings.powerSave=conf.getInt("powerSave",POWER_SAVE_DEFAULT); + settings.absorbInsInput=conf.getInt("absorbInsInput",0); + settings.eventDelay=conf.getInt("eventDelay",0); + settings.moveWindowTitle=conf.getInt("moveWindowTitle",1); + settings.hiddenSystems=conf.getInt("hiddenSystems",0); + settings.horizontalDataView=conf.getInt("horizontalDataView",0); + settings.noMultiSystem=conf.getInt("noMultiSystem",0); + settings.oldMacroVSlider=conf.getInt("oldMacroVSlider",0); + settings.displayAllInsTypes=conf.getInt("displayAllInsTypes",0); + settings.displayPartial=conf.getInt("displayPartial",0); + settings.noteCellSpacing=conf.getInt("noteCellSpacing",0); + settings.insCellSpacing=conf.getInt("insCellSpacing",0); + settings.volCellSpacing=conf.getInt("volCellSpacing",0); + settings.effectCellSpacing=conf.getInt("effectCellSpacing",0); + settings.effectValCellSpacing=conf.getInt("effectValCellSpacing",0); + settings.doubleClickColumn=conf.getInt("doubleClickColumn",1); + settings.blankIns=conf.getInt("blankIns",0); + settings.dragMovesSelection=conf.getInt("dragMovesSelection",2); + settings.unsignedDetune=conf.getInt("unsignedDetune",0); + settings.noThreadedInput=conf.getInt("noThreadedInput",0); + settings.saveWindowPos=conf.getInt("saveWindowPos",1); + settings.initialSysName=conf.getString("initialSysName",""); + settings.clampSamples=conf.getInt("clampSamples",0); + settings.noteOffLabel=conf.getString("noteOffLabel","OFF"); + settings.noteRelLabel=conf.getString("noteRelLabel","==="); + settings.macroRelLabel=conf.getString("macroRelLabel","REL"); + settings.emptyLabel=conf.getString("emptyLabel","..."); + settings.emptyLabel2=conf.getString("emptyLabel2",".."); + settings.saveUnusedPatterns=conf.getInt("saveUnusedPatterns",0); + settings.channelColors=conf.getInt("channelColors",1); + settings.channelTextColors=conf.getInt("channelTextColors",0); + settings.channelStyle=conf.getInt("channelStyle",1); + settings.channelVolStyle=conf.getInt("channelVolStyle",0); + settings.channelFeedbackStyle=conf.getInt("channelFeedbackStyle",1); + settings.channelFont=conf.getInt("channelFont",1); + settings.channelTextCenter=conf.getInt("channelTextCenter",1); + settings.maxRecentFile=conf.getInt("maxRecentFile",10); + settings.midiOutClock=conf.getInt("midiOutClock",0); + settings.midiOutTime=conf.getInt("midiOutTime",0); + settings.midiOutProgramChange=conf.getInt("midiOutProgramChange",0); + settings.midiOutMode=conf.getInt("midiOutMode",1); + settings.midiOutTimeRate=conf.getInt("midiOutTimeRate",0); + settings.centerPattern=conf.getInt("centerPattern",0); + settings.ordersCursor=conf.getInt("ordersCursor",1); + settings.persistFadeOut=conf.getInt("persistFadeOut",1); + settings.exportLoops=conf.getInt("exportLoops",0); + settings.exportFadeOut=conf.getDouble("exportFadeOut",0.0); + settings.macroLayout=conf.getInt("macroLayout",0); + settings.doubleClickTime=conf.getFloat("doubleClickTime",0.3f); + settings.oneDigitEffects=conf.getInt("oneDigitEffects",0); + settings.disableFadeIn=conf.getInt("disableFadeIn",0); + settings.alwaysPlayIntro=conf.getInt("alwaysPlayIntro",0); + settings.cursorFollowsOrder=conf.getInt("cursorFollowsOrder",1); + settings.iCannotWait=conf.getInt("iCannotWait",0); + settings.orderButtonPos=conf.getInt("orderButtonPos",2); + settings.compress=conf.getInt("compress",1); + settings.newPatternFormat=conf.getInt("newPatternFormat",1); + settings.renderBackend=conf.getString("renderBackend",GUI_BACKEND_DEFAULT_NAME); + settings.renderClearPos=conf.getInt("renderClearPos",0); + settings.insertBehavior=conf.getInt("insertBehavior",1); + settings.pullDeleteRow=conf.getInt("pullDeleteRow",1); + settings.newSongBehavior=conf.getInt("newSongBehavior",0); + settings.memUsageUnit=conf.getInt("memUsageUnit",1); + settings.cursorFollowsWheel=conf.getInt("cursorFollowsWheel",0); + settings.noDMFCompat=conf.getInt("noDMFCompat",0); + settings.removeInsOff=conf.getInt("removeInsOff",0); + settings.removeVolOff=conf.getInt("removeVolOff",0); + settings.playOnLoad=conf.getInt("playOnLoad",0); + settings.insTypeMenu=conf.getInt("insTypeMenu",1); + settings.capitalMenuBar=conf.getInt("capitalMenuBar",0); + settings.centerPopup=conf.getInt("centerPopup",1); + settings.insIconsStyle=conf.getInt("insIconsStyle",1); + settings.classicChipOptions=conf.getInt("classicChipOptions",0); + settings.wasapiEx=conf.getInt("wasapiEx",0); + settings.chanOscThreads=conf.getInt("chanOscThreads",0); + settings.renderPoolThreads=conf.getInt("renderPoolThreads",0); + settings.showPool=conf.getInt("showPool",0); + settings.writeInsNames=conf.getInt("writeInsNames",1); + settings.readInsNames=conf.getInt("readInsNames",1); + settings.defaultAuthorName=conf.getString("defaultAuthorName",""); + settings.fontBackend=conf.getInt("fontBackend",FONT_BACKEND_DEFAULT); + settings.fontHinting=conf.getInt("fontHinting",0); + settings.fontBitmap=conf.getInt("fontBitmap",0); + settings.fontAutoHint=conf.getInt("fontAutoHint",1); + settings.fontAntiAlias=conf.getInt("fontAntiAlias",1); + settings.selectAssetOnLoad=conf.getInt("selectAssetOnLoad",1); + settings.basicColors=conf.getInt("basicColors",1); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.headFontSize,2,96); @@ -4051,10 +4051,24 @@ void FurnaceGUI::syncSettings() { if (settings.exportLoops<0.0) settings.exportLoops=0.0; if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0; - String initialSys2=e->getConfString("initialSys2",""); - bool oldVol=e->getConfInt("configVersion",DIV_ENGINE_VERSION)<135; + // keybinds + for (int i=0; idecodeSysDesc(e->getConfString("initialSys","")); + initialSys2=e->decodeSysDesc(conf.getString("initialSys","")); oldVol=false; } settings.initialSys.clear(); @@ -4081,18 +4095,225 @@ void FurnaceGUI::syncSettings() { settings.initialSys.set(fmt::sprintf("vol%d",i),newVol); settings.initialSys.set(fmt::sprintf("pan%d",i),newPan); } - e->setConf("initialSys2",settings.initialSys.toBase64()); - e->setConf("configVersion",DIV_ENGINE_VERSION); + conf.set("initialSys2",settings.initialSys.toBase64()); + conf.set("configVersion",DIV_ENGINE_VERSION); } } +} + +void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { + conf.set("mainFontSize",settings.mainFontSize); + conf.set("headFontSize",settings.headFontSize); + conf.set("patFontSize",settings.patFontSize); + conf.set("iconSize",settings.iconSize); + conf.set("audioEngine",String(audioBackends[settings.audioEngine])); + conf.set("audioDevice",settings.audioDevice); + conf.set("midiInDevice",settings.midiInDevice); + conf.set("midiOutDevice",settings.midiOutDevice); + conf.set("renderDriver",settings.renderDriver); + conf.set("sdlAudioDriver",settings.sdlAudioDriver); + conf.set("audioQuality",settings.audioQuality); + conf.set("audioHiPass",settings.audioHiPass); + conf.set("audioBufSize",settings.audioBufSize); + conf.set("audioRate",settings.audioRate); + conf.set("audioChans",settings.audioChans); + conf.set("arcadeCore",settings.arcadeCore); + conf.set("ym2612Core",settings.ym2612Core); + conf.set("snCore",settings.snCore); + conf.set("nesCore",settings.nesCore); + conf.set("fdsCore",settings.fdsCore); + conf.set("c64Core",settings.c64Core); + conf.set("pokeyCore",settings.pokeyCore); + conf.set("opnCore",settings.opnCore); + conf.set("opl2Core",settings.opl2Core); + conf.set("opl3Core",settings.opl3Core); + conf.set("arcadeCoreRender",settings.arcadeCoreRender); + conf.set("ym2612CoreRender",settings.ym2612CoreRender); + conf.set("snCoreRender",settings.snCoreRender); + conf.set("nesCoreRender",settings.nesCoreRender); + conf.set("fdsCoreRender",settings.fdsCoreRender); + conf.set("c64CoreRender",settings.c64CoreRender); + conf.set("pokeyCoreRender",settings.pokeyCoreRender); + conf.set("opnCoreRender",settings.opnCoreRender); + conf.set("opl2CoreRender",settings.opl2CoreRender); + conf.set("opl3CoreRender",settings.opl3CoreRender); + conf.set("pcSpeakerOutMethod",settings.pcSpeakerOutMethod); + conf.set("yrw801Path",settings.yrw801Path); + conf.set("tg100Path",settings.tg100Path); + conf.set("mu5Path",settings.mu5Path); + conf.set("mainFont",settings.mainFont); + conf.set("headFont",settings.headFont); + conf.set("patFont",settings.patFont); + conf.set("mainFontPath",settings.mainFontPath); + conf.set("headFontPath",settings.headFontPath); + conf.set("patFontPath",settings.patFontPath); + conf.set("patRowsBase",settings.patRowsBase); + conf.set("orderRowsBase",settings.orderRowsBase); + conf.set("soloAction",settings.soloAction); + conf.set("pullDeleteBehavior",settings.pullDeleteBehavior); + conf.set("wrapHorizontal",settings.wrapHorizontal); + conf.set("wrapVertical",settings.wrapVertical); + conf.set("macroView",settings.macroView); + conf.set("fmNames",settings.fmNames); + conf.set("allowEditDocking",settings.allowEditDocking); + conf.set("chipNames",settings.chipNames); + conf.set("overflowHighlight",settings.overflowHighlight); + conf.set("partyTime",settings.partyTime); + conf.set("flatNotes",settings.flatNotes); + conf.set("germanNotation",settings.germanNotation); + conf.set("stepOnDelete",settings.stepOnDelete); + conf.set("scrollStep",settings.scrollStep); + conf.set("sysSeparators",settings.sysSeparators); + conf.set("forceMono",settings.forceMono); + conf.set("controlLayout",settings.controlLayout); + conf.set("statusDisplay",settings.statusDisplay); + conf.set("dpiScale",settings.dpiScale); + conf.set("viewPrevPattern",settings.viewPrevPattern); + conf.set("guiColorsBase",settings.guiColorsBase); + conf.set("guiColorsShading",settings.guiColorsShading); + conf.set("avoidRaisingPattern",settings.avoidRaisingPattern); + conf.set("insFocusesPattern",settings.insFocusesPattern); + conf.set("stepOnInsert",settings.stepOnInsert); + conf.set("unifiedDataView",settings.unifiedDataView); + conf.set("sysFileDialog",settings.sysFileDialog); + conf.set("roundedWindows",settings.roundedWindows); + conf.set("roundedButtons",settings.roundedButtons); + conf.set("roundedMenus",settings.roundedMenus); + conf.set("loadJapanese",settings.loadJapanese); + conf.set("loadChinese",settings.loadChinese); + conf.set("loadChineseTraditional",settings.loadChineseTraditional); + conf.set("loadKorean",settings.loadKorean); + conf.set("fmLayout",settings.fmLayout); + conf.set("sampleLayout",settings.sampleLayout); + conf.set("waveLayout",settings.waveLayout); + conf.set("susPosition",settings.susPosition); + conf.set("effectCursorDir",settings.effectCursorDir); + conf.set("cursorPastePos",settings.cursorPastePos); + conf.set("titleBarInfo",settings.titleBarInfo); + conf.set("titleBarSys",settings.titleBarSys); + conf.set("frameBorders",settings.frameBorders); + conf.set("effectDeletionAltersValue",settings.effectDeletionAltersValue); + conf.set("oscRoundedCorners",settings.oscRoundedCorners); + conf.set("oscTakesEntireWindow",settings.oscTakesEntireWindow); + conf.set("oscBorder",settings.oscBorder); + conf.set("oscEscapesBoundary",settings.oscEscapesBoundary); + conf.set("oscMono",settings.oscMono); + conf.set("oscAntiAlias",settings.oscAntiAlias); + conf.set("separateFMColors",settings.separateFMColors); + conf.set("insEditColorize",settings.insEditColorize); + conf.set("metroVol",settings.metroVol); + conf.set("sampleVol",settings.sampleVol); + conf.set("pushNibble",settings.pushNibble); + conf.set("scrollChangesOrder",settings.scrollChangesOrder); + conf.set("oplStandardWaveNames",settings.oplStandardWaveNames); + conf.set("cursorMoveNoScroll",settings.cursorMoveNoScroll); + conf.set("lowLatency",settings.lowLatency); + conf.set("notePreviewBehavior",settings.notePreviewBehavior); + conf.set("powerSave",settings.powerSave); + conf.set("absorbInsInput",settings.absorbInsInput); + conf.set("eventDelay",settings.eventDelay); + conf.set("moveWindowTitle",settings.moveWindowTitle); + conf.set("hiddenSystems",settings.hiddenSystems); + conf.set("initialSys2",settings.initialSys.toBase64()); + conf.set("initialSysName",settings.initialSysName); + conf.set("horizontalDataView",settings.horizontalDataView); + conf.set("noMultiSystem",settings.noMultiSystem); + conf.set("oldMacroVSlider",settings.oldMacroVSlider); + conf.set("displayAllInsTypes",settings.displayAllInsTypes); + conf.set("displayPartial",settings.displayPartial); + conf.set("noteCellSpacing",settings.noteCellSpacing); + conf.set("insCellSpacing",settings.insCellSpacing); + conf.set("volCellSpacing",settings.volCellSpacing); + conf.set("effectCellSpacing",settings.effectCellSpacing); + conf.set("effectValCellSpacing",settings.effectValCellSpacing); + conf.set("doubleClickColumn",settings.doubleClickColumn); + conf.set("blankIns",settings.blankIns); + conf.set("dragMovesSelection",settings.dragMovesSelection); + conf.set("unsignedDetune",settings.unsignedDetune); + conf.set("noThreadedInput",settings.noThreadedInput); + conf.set("saveWindowPos",settings.saveWindowPos); + conf.set("clampSamples",settings.clampSamples); + conf.set("noteOffLabel",settings.noteOffLabel); + conf.set("noteRelLabel",settings.noteRelLabel); + conf.set("macroRelLabel",settings.macroRelLabel); + conf.set("emptyLabel",settings.emptyLabel); + conf.set("emptyLabel2",settings.emptyLabel2); + conf.set("saveUnusedPatterns",settings.saveUnusedPatterns); + conf.set("channelColors",settings.channelColors); + conf.set("channelTextColors",settings.channelTextColors); + conf.set("channelStyle",settings.channelStyle); + conf.set("channelVolStyle",settings.channelVolStyle); + conf.set("channelFeedbackStyle",settings.channelFeedbackStyle); + conf.set("channelFont",settings.channelFont); + conf.set("channelTextCenter",settings.channelTextCenter); + conf.set("maxRecentFile",settings.maxRecentFile); + conf.set("midiOutClock",settings.midiOutClock); + conf.set("midiOutTime",settings.midiOutTime); + conf.set("midiOutProgramChange",settings.midiOutProgramChange); + conf.set("midiOutMode",settings.midiOutMode); + conf.set("midiOutTimeRate",settings.midiOutTimeRate); + conf.set("centerPattern",settings.centerPattern); + conf.set("ordersCursor",settings.ordersCursor); + conf.set("persistFadeOut",settings.persistFadeOut); + conf.set("exportLoops",settings.exportLoops); + conf.set("exportFadeOut",settings.exportFadeOut); + conf.set("macroLayout",settings.macroLayout); + conf.set("doubleClickTime",settings.doubleClickTime); + conf.set("oneDigitEffects",settings.oneDigitEffects); + conf.set("disableFadeIn",settings.disableFadeIn); + conf.set("alwaysPlayIntro",settings.alwaysPlayIntro); + conf.set("cursorFollowsOrder",settings.cursorFollowsOrder); + conf.set("iCannotWait",settings.iCannotWait); + conf.set("orderButtonPos",settings.orderButtonPos); + conf.set("compress",settings.compress); + conf.set("newPatternFormat",settings.newPatternFormat); + conf.set("renderBackend",settings.renderBackend); + conf.set("renderClearPos",settings.renderClearPos); + conf.set("insertBehavior",settings.insertBehavior); + conf.set("pullDeleteRow",settings.pullDeleteRow); + conf.set("newSongBehavior",settings.newSongBehavior); + conf.set("memUsageUnit",settings.memUsageUnit); + conf.set("cursorFollowsWheel",settings.cursorFollowsWheel); + conf.set("noDMFCompat",settings.noDMFCompat); + conf.set("removeInsOff",settings.removeInsOff); + conf.set("removeVolOff",settings.removeVolOff); + conf.set("playOnLoad",settings.playOnLoad); + conf.set("insTypeMenu",settings.insTypeMenu); + conf.set("capitalMenuBar",settings.capitalMenuBar); + conf.set("centerPopup",settings.centerPopup); + conf.set("insIconsStyle",settings.insIconsStyle); + conf.set("classicChipOptions",settings.classicChipOptions); + conf.set("wasapiEx",settings.wasapiEx); + conf.set("chanOscThreads",settings.chanOscThreads); + conf.set("renderPoolThreads",settings.renderPoolThreads); + conf.set("showPool",settings.showPool); + conf.set("writeInsNames",settings.writeInsNames); + conf.set("readInsNames",settings.readInsNames); + conf.set("defaultAuthorName",settings.defaultAuthorName); + conf.set("fontBackend",settings.fontBackend); + conf.set("fontHinting",settings.fontHinting); + conf.set("fontBitmap",settings.fontBitmap); + conf.set("fontAutoHint",settings.fontAutoHint); + conf.set("fontAntiAlias",settings.fontAntiAlias); + conf.set("selectAssetOnLoad",settings.selectAssetOnLoad); + conf.set("basicColors",settings.basicColors); + + // colors + for (int i=0; igetConfInt(String("keybind_GUI_ACTION_")+String(guiActions[i].name),guiActions[i].defaultBind); + conf.set(String("keybind_GUI_ACTION_")+String(guiActions[i].name),actionKeys[i]); } - decodeKeyMap(noteKeys,e->getConfString("noteKeys",DEFAULT_NOTE_KEYS)); + conf.set("noteKeys",encodeKeyMap(noteKeys)); +} + +void FurnaceGUI::syncSettings() { + readConfig(e->getConfObject()); parseKeybinds(); @@ -4136,217 +4357,10 @@ void FurnaceGUI::commitSettings() { settings.audioHiPass!=e->getConfInt("audioHiPass",1) ); - e->setConf("mainFontSize",settings.mainFontSize); - e->setConf("headFontSize",settings.headFontSize); - e->setConf("patFontSize",settings.patFontSize); - e->setConf("iconSize",settings.iconSize); - e->setConf("audioEngine",String(audioBackends[settings.audioEngine])); - e->setConf("audioDevice",settings.audioDevice); - e->setConf("midiInDevice",settings.midiInDevice); - e->setConf("midiOutDevice",settings.midiOutDevice); - e->setConf("renderDriver",settings.renderDriver); - e->setConf("sdlAudioDriver",settings.sdlAudioDriver); - e->setConf("audioQuality",settings.audioQuality); - e->setConf("audioHiPass",settings.audioHiPass); - e->setConf("audioBufSize",settings.audioBufSize); - e->setConf("audioRate",settings.audioRate); - e->setConf("audioChans",settings.audioChans); - e->setConf("arcadeCore",settings.arcadeCore); - e->setConf("ym2612Core",settings.ym2612Core); - e->setConf("snCore",settings.snCore); - e->setConf("nesCore",settings.nesCore); - e->setConf("fdsCore",settings.fdsCore); - e->setConf("c64Core",settings.c64Core); - e->setConf("pokeyCore",settings.pokeyCore); - e->setConf("opnCore",settings.opnCore); - e->setConf("opl2Core",settings.opl2Core); - e->setConf("opl3Core",settings.opl3Core); - e->setConf("arcadeCoreRender",settings.arcadeCoreRender); - e->setConf("ym2612CoreRender",settings.ym2612CoreRender); - e->setConf("snCoreRender",settings.snCoreRender); - e->setConf("nesCoreRender",settings.nesCoreRender); - e->setConf("fdsCoreRender",settings.fdsCoreRender); - e->setConf("c64CoreRender",settings.c64CoreRender); - e->setConf("pokeyCoreRender",settings.pokeyCoreRender); - e->setConf("opnCoreRender",settings.opnCoreRender); - e->setConf("opl2CoreRender",settings.opl2CoreRender); - e->setConf("opl3CoreRender",settings.opl3CoreRender); - e->setConf("pcSpeakerOutMethod",settings.pcSpeakerOutMethod); - e->setConf("yrw801Path",settings.yrw801Path); - e->setConf("tg100Path",settings.tg100Path); - e->setConf("mu5Path",settings.mu5Path); - e->setConf("mainFont",settings.mainFont); - e->setConf("headFont",settings.headFont); - e->setConf("patFont",settings.patFont); - e->setConf("mainFontPath",settings.mainFontPath); - e->setConf("headFontPath",settings.headFontPath); - e->setConf("patFontPath",settings.patFontPath); - e->setConf("patRowsBase",settings.patRowsBase); - e->setConf("orderRowsBase",settings.orderRowsBase); - e->setConf("soloAction",settings.soloAction); - e->setConf("pullDeleteBehavior",settings.pullDeleteBehavior); - e->setConf("wrapHorizontal",settings.wrapHorizontal); - e->setConf("wrapVertical",settings.wrapVertical); - e->setConf("macroView",settings.macroView); - e->setConf("fmNames",settings.fmNames); - e->setConf("allowEditDocking",settings.allowEditDocking); - e->setConf("chipNames",settings.chipNames); - e->setConf("overflowHighlight",settings.overflowHighlight); - e->setConf("partyTime",settings.partyTime); - e->setConf("flatNotes",settings.flatNotes); - e->setConf("germanNotation",settings.germanNotation); - e->setConf("stepOnDelete",settings.stepOnDelete); - e->setConf("scrollStep",settings.scrollStep); - e->setConf("sysSeparators",settings.sysSeparators); - e->setConf("forceMono",settings.forceMono); - e->setConf("controlLayout",settings.controlLayout); - e->setConf("statusDisplay",settings.statusDisplay); - e->setConf("dpiScale",settings.dpiScale); - e->setConf("viewPrevPattern",settings.viewPrevPattern); - e->setConf("guiColorsBase",settings.guiColorsBase); - e->setConf("guiColorsShading",settings.guiColorsShading); - e->setConf("avoidRaisingPattern",settings.avoidRaisingPattern); - e->setConf("insFocusesPattern",settings.insFocusesPattern); - e->setConf("stepOnInsert",settings.stepOnInsert); - e->setConf("unifiedDataView",settings.unifiedDataView); - e->setConf("sysFileDialog",settings.sysFileDialog); - e->setConf("roundedWindows",settings.roundedWindows); - e->setConf("roundedButtons",settings.roundedButtons); - e->setConf("roundedMenus",settings.roundedMenus); - e->setConf("loadJapanese",settings.loadJapanese); - e->setConf("loadChinese",settings.loadChinese); - e->setConf("loadChineseTraditional",settings.loadChineseTraditional); - e->setConf("loadKorean",settings.loadKorean); - e->setConf("fmLayout",settings.fmLayout); - e->setConf("sampleLayout",settings.sampleLayout); - e->setConf("waveLayout",settings.waveLayout); - e->setConf("susPosition",settings.susPosition); - e->setConf("effectCursorDir",settings.effectCursorDir); - e->setConf("cursorPastePos",settings.cursorPastePos); - e->setConf("titleBarInfo",settings.titleBarInfo); - e->setConf("titleBarSys",settings.titleBarSys); - e->setConf("frameBorders",settings.frameBorders); - e->setConf("effectDeletionAltersValue",settings.effectDeletionAltersValue); - e->setConf("oscRoundedCorners",settings.oscRoundedCorners); - e->setConf("oscTakesEntireWindow",settings.oscTakesEntireWindow); - e->setConf("oscBorder",settings.oscBorder); - e->setConf("oscEscapesBoundary",settings.oscEscapesBoundary); - e->setConf("oscMono",settings.oscMono); - e->setConf("oscAntiAlias",settings.oscAntiAlias); - e->setConf("separateFMColors",settings.separateFMColors); - e->setConf("insEditColorize",settings.insEditColorize); - e->setConf("metroVol",settings.metroVol); - e->setConf("sampleVol",settings.sampleVol); - e->setConf("pushNibble",settings.pushNibble); - e->setConf("scrollChangesOrder",settings.scrollChangesOrder); - e->setConf("oplStandardWaveNames",settings.oplStandardWaveNames); - e->setConf("cursorMoveNoScroll",settings.cursorMoveNoScroll); - e->setConf("lowLatency",settings.lowLatency); - e->setConf("notePreviewBehavior",settings.notePreviewBehavior); - e->setConf("powerSave",settings.powerSave); - e->setConf("absorbInsInput",settings.absorbInsInput); - e->setConf("eventDelay",settings.eventDelay); - e->setConf("moveWindowTitle",settings.moveWindowTitle); - e->setConf("hiddenSystems",settings.hiddenSystems); - e->setConf("initialSys2",settings.initialSys.toBase64()); - e->setConf("initialSysName",settings.initialSysName); - e->setConf("horizontalDataView",settings.horizontalDataView); - e->setConf("noMultiSystem",settings.noMultiSystem); - e->setConf("oldMacroVSlider",settings.oldMacroVSlider); - e->setConf("displayAllInsTypes",settings.displayAllInsTypes); - e->setConf("displayPartial",settings.displayPartial); - e->setConf("noteCellSpacing",settings.noteCellSpacing); - e->setConf("insCellSpacing",settings.insCellSpacing); - e->setConf("volCellSpacing",settings.volCellSpacing); - e->setConf("effectCellSpacing",settings.effectCellSpacing); - e->setConf("effectValCellSpacing",settings.effectValCellSpacing); - e->setConf("doubleClickColumn",settings.doubleClickColumn); - e->setConf("blankIns",settings.blankIns); - e->setConf("dragMovesSelection",settings.dragMovesSelection); - e->setConf("unsignedDetune",settings.unsignedDetune); - e->setConf("noThreadedInput",settings.noThreadedInput); - e->setConf("saveWindowPos",settings.saveWindowPos); - e->setConf("clampSamples",settings.clampSamples); - e->setConf("noteOffLabel",settings.noteOffLabel); - e->setConf("noteRelLabel",settings.noteRelLabel); - e->setConf("macroRelLabel",settings.macroRelLabel); - e->setConf("emptyLabel",settings.emptyLabel); - e->setConf("emptyLabel2",settings.emptyLabel2); - e->setConf("saveUnusedPatterns",settings.saveUnusedPatterns); - e->setConf("channelColors",settings.channelColors); - e->setConf("channelTextColors",settings.channelTextColors); - e->setConf("channelStyle",settings.channelStyle); - e->setConf("channelVolStyle",settings.channelVolStyle); - e->setConf("channelFeedbackStyle",settings.channelFeedbackStyle); - e->setConf("channelFont",settings.channelFont); - e->setConf("channelTextCenter",settings.channelTextCenter); - e->setConf("maxRecentFile",settings.maxRecentFile); - e->setConf("midiOutClock",settings.midiOutClock); - e->setConf("midiOutTime",settings.midiOutTime); - e->setConf("midiOutProgramChange",settings.midiOutProgramChange); - e->setConf("midiOutMode",settings.midiOutMode); - e->setConf("midiOutTimeRate",settings.midiOutTimeRate); - 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); - e->setConf("macroLayout",settings.macroLayout); - e->setConf("doubleClickTime",settings.doubleClickTime); - e->setConf("oneDigitEffects",settings.oneDigitEffects); - e->setConf("disableFadeIn",settings.disableFadeIn); - e->setConf("alwaysPlayIntro",settings.alwaysPlayIntro); - e->setConf("cursorFollowsOrder",settings.cursorFollowsOrder); - e->setConf("iCannotWait",settings.iCannotWait); - e->setConf("orderButtonPos",settings.orderButtonPos); - e->setConf("compress",settings.compress); - e->setConf("newPatternFormat",settings.newPatternFormat); - e->setConf("renderBackend",settings.renderBackend); - e->setConf("renderClearPos",settings.renderClearPos); - e->setConf("insertBehavior",settings.insertBehavior); - e->setConf("pullDeleteRow",settings.pullDeleteRow); - e->setConf("newSongBehavior",settings.newSongBehavior); - e->setConf("memUsageUnit",settings.memUsageUnit); - e->setConf("cursorFollowsWheel",settings.cursorFollowsWheel); - e->setConf("noDMFCompat",settings.noDMFCompat); - e->setConf("removeInsOff",settings.removeInsOff); - e->setConf("removeVolOff",settings.removeVolOff); - e->setConf("playOnLoad",settings.playOnLoad); - e->setConf("insTypeMenu",settings.insTypeMenu); - e->setConf("capitalMenuBar",settings.capitalMenuBar); - e->setConf("centerPopup",settings.centerPopup); - e->setConf("insIconsStyle",settings.insIconsStyle); - e->setConf("classicChipOptions",settings.classicChipOptions); - e->setConf("wasapiEx",settings.wasapiEx); - e->setConf("chanOscThreads",settings.chanOscThreads); - e->setConf("renderPoolThreads",settings.renderPoolThreads); - e->setConf("showPool",settings.showPool); - e->setConf("writeInsNames",settings.writeInsNames); - e->setConf("readInsNames",settings.readInsNames); - e->setConf("defaultAuthorName",settings.defaultAuthorName); - e->setConf("fontBackend",settings.fontBackend); - e->setConf("fontHinting",settings.fontHinting); - e->setConf("fontBitmap",settings.fontBitmap); - e->setConf("fontAutoHint",settings.fontAutoHint); - e->setConf("fontAntiAlias",settings.fontAntiAlias); - e->setConf("selectAssetOnLoad",settings.selectAssetOnLoad); - e->setConf("basicColors",settings.basicColors); - - // colors - for (int i=0; isetConf(guiColors[i].name,(int)ImGui::ColorConvertFloat4ToU32(uiColors[i])); - } - - // keybinds - for (int i=0; isetConf(String("keybind_GUI_ACTION_")+String(guiActions[i].name),actionKeys[i]); - } + writeConfig(e->getConfObject()); parseKeybinds(); - e->setConf("noteKeys",encodeKeyMap(noteKeys)); - midiMap.compile(); midiMap.write(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); @@ -4853,13 +4867,6 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { chanOscWorkPool=NULL; } - // colors - if (updateFonts) { - for (int i=0; igetConfInt(guiColors[i].name,guiColors[i].defaultColor)); - } - } - for (int i=0; i<64; i++) { ImVec4 col1=uiColors[GUI_COLOR_PATTERN_VOLUME_MIN]; ImVec4 col2=uiColors[GUI_COLOR_PATTERN_VOLUME_HALF]; From 33c26266f3d7458aa27f17528e96b1249e92438b Mon Sep 17 00:00:00 2001 From: Electric Keet Date: Thu, 21 Dec 2023 15:21:17 -0800 Subject: [PATCH 25/53] Document "Guru mode". --- doc/2-interface/settings.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/2-interface/settings.md b/doc/2-interface/settings.md index b3a93b04c..369ab0b58 100644 --- a/doc/2-interface/settings.md +++ b/doc/2-interface/settings.md @@ -509,6 +509,7 @@ below all the binds, select a key from the dropdown list to add it. it will appe - **Import** - **Export** - **Reset defaults** +- **Guru mode**: exposes additional color settings that would otherwise be automatically calculated. - **General** - **Color scheme type:** - **Dark** From a7d6728b44e8441c81d22e3e8083ac2125d4f2bf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 21 Dec 2023 18:53:47 -0500 Subject: [PATCH 26/53] asdf --- doc/2-interface/settings.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/2-interface/settings.md b/doc/2-interface/settings.md index 369ab0b58..f65f23fcb 100644 --- a/doc/2-interface/settings.md +++ b/doc/2-interface/settings.md @@ -498,7 +498,7 @@ below all the binds, select a key from the dropdown list to add it. it will appe - **Rounded window corners** - **Rounded buttons** - **Rounded menu corners** -- **Borders around widgets**: draws thin borders on buttons, checkboxes, text widgets, and the like. +- **Borders around widgets**: draws borders on buttons, checkboxes, text widgets, and the like. @@ -509,7 +509,7 @@ below all the binds, select a key from the dropdown list to add it. it will appe - **Import** - **Export** - **Reset defaults** -- **Guru mode**: exposes additional color settings that would otherwise be automatically calculated. +- **Guru mode**: exposes all color options (instead of accent colors). - **General** - **Color scheme type:** - **Dark** From df3e3e8aeceb966a74e27492818ae53977671d39 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 21 Dec 2023 19:57:26 -0500 Subject: [PATCH 27/53] bunch of code de-duplication --- src/gui/exportOptions.cpp | 374 +++++++++++++++++++++----------------- src/gui/gui.cpp | 171 +---------------- src/gui/gui.h | 9 +- 3 files changed, 219 insertions(+), 335 deletions(-) diff --git a/src/gui/exportOptions.cpp b/src/gui/exportOptions.cpp index 4e3982dec..d5765fe2b 100644 --- a/src/gui/exportOptions.cpp +++ b/src/gui/exportOptions.cpp @@ -22,144 +22,224 @@ #include "../fileutils.h" #include "misc/cpp/imgui_stdlib.h" -void FurnaceGUI::drawExport() { +void FurnaceGUI::drawExportAudio() { exitDisabledTimer=1; + ImGui::RadioButton("one file",&audioExportType,0); + ImGui::RadioButton("multiple files (one per chip)",&audioExportType,1); + ImGui::RadioButton("multiple files (one per channel)",&audioExportType,2); + if (ImGui::InputInt("Loops",&exportLoops,1,2)) { + if (exportLoops<0) exportLoops=0; + } + if (ImGui::InputDouble("Fade out (seconds)",&exportFadeOut,1.0,2.0,"%.1f")) { + if (exportFadeOut<0.0) exportFadeOut=0.0; + } + + if (ImGui::Button("Export")) { + switch (audioExportType) { + case 0: + openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); + break; + case 1: + openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); + break; + case 2: + openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL); + break; + } + ImGui::CloseCurrentPopup(); + } +} + +void FurnaceGUI::drawExportVGM() { + exitDisabledTimer=1; + + ImGui::Text("settings:"); + if (ImGui::BeginCombo("format version",fmt::sprintf("%d.%.2x",vgmExportVersion>>8,vgmExportVersion&0xff).c_str())) { + for (int i=0; i<7; i++) { + if (ImGui::Selectable(fmt::sprintf("%d.%.2x",vgmVersions[i]>>8,vgmVersions[i]&0xff).c_str(),vgmExportVersion==vgmVersions[i])) { + vgmExportVersion=vgmVersions[i]; + } + } + ImGui::EndCombo(); + } + ImGui::Checkbox("loop",&vgmExportLoop); + if (vgmExportLoop && e->song.loopModality==2) { + ImGui::Text("loop trail:"); + ImGui::Indent(); + if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) { + vgmExportTrailingTicks=-1; + } + if (ImGui::RadioButton("add one loop",vgmExportTrailingTicks==-2)) { + vgmExportTrailingTicks=-2; + } + if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) { + vgmExportTrailingTicks=0; + } + if (vgmExportTrailingTicks>=0) { + ImGui::SameLine(); + if (ImGui::InputInt("##TrailTicks",&vgmExportTrailingTicks,1,100)) { + if (vgmExportTrailingTicks<0) vgmExportTrailingTicks=0; + } + } + ImGui::Unindent(); + } + ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "inserts data blocks on pattern changes.\n" + "useful if you are writing a playback routine.\n\n" + + "the format of a pattern change data block is:\n" + "67 66 FE ll ll ll ll 01 oo rr pp pp pp ...\n" + "- ll: length, a 32-bit little-endian number\n" + "- oo: order\n" + "- rr: initial row (a 0Dxx effect is able to select a different row)\n" + "- pp: pattern index (one per channel)\n\n" + + "pattern indexes are ordered as they appear in the song." + ); + } + ImGui::Checkbox("direct stream mode",&vgmExportDirectStream); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "required for DualPCM and MSM6258 export.\n\n" + "allows for volume/direction changes when playing samples,\n" + "at the cost of a massive increase in file size." + ); + } + ImGui::Text("chips to export:"); + bool hasOneAtLeast=false; + for (int i=0; isong.systemLen; i++) { + int minVersion=e->minVGMVersion(e->song.system[i]); + ImGui::BeginDisabled(minVersion>vgmExportVersion || minVersion==0); + ImGui::Checkbox(fmt::sprintf("%d. %s##_SYSV%d",i+1,getSystemName(e->song.system[i]),i).c_str(),&willExport[i]); + ImGui::EndDisabled(); + if (minVersion>vgmExportVersion) { + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + ImGui::SetTooltip("this chip is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); + } + } else if (minVersion==0) { + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + ImGui::SetTooltip("this chip is not supported by the VGM format!"); + } + } else { + if (willExport[i]) hasOneAtLeast=true; + } + } + ImGui::Text("select the chip you wish to export, but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); + if (hasOneAtLeast) { + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_VGM); + ImGui::CloseCurrentPopup(); + } + } else { + ImGui::Text("nothing to export"); + } +} + +void FurnaceGUI::drawExportZSM() { + exitDisabledTimer=1; + + ImGui::Text("Commander X16 Zsound Music File"); + if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) { + if (zsmExportTickRate<1) zsmExportTickRate=1; + if (zsmExportTickRate>44100) zsmExportTickRate=44100; + } + ImGui::Checkbox("loop",&zsmExportLoop); + ImGui::SameLine(); + ImGui::Checkbox("optimize size",&zsmExportOptimize); + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_ZSM); + ImGui::CloseCurrentPopup(); + } +} + +void FurnaceGUI::drawExportAmigaVal() { + exitDisabledTimer=1; + + ImGui::Text( + "this is NOT ROM export! only use for making sure the\n" + "Furnace Amiga emulator is working properly by\n" + "comparing it with real Amiga output." + ); + ImGui::AlignTextToFramePadding(); + ImGui::Text("Directory"); + ImGui::SameLine(); + ImGui::InputText("##AVDPath",&workingDirROMExport); + if (ImGui::Button("Bake Data")) { + std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); + if (workingDirROMExport.size()>0) { + if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; + } + for (DivROMExportOutput& i: out) { + String path=workingDirROMExport+i.name; + FILE* outFile=ps_fopen(path.c_str(),"wb"); + if (outFile!=NULL) { + fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); + fclose(outFile); + } + i.data->finish(); + delete i.data; + } + showError(fmt::sprintf("Done! Baked %d files.",(int)out.size())); + ImGui::CloseCurrentPopup(); + } +} + +void FurnaceGUI::drawExportText() { + exitDisabledTimer=1; + + ImGui::Text( + "this option exports the song to a text file.\n" + ); + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_TEXT); + ImGui::CloseCurrentPopup(); + } +} + +void FurnaceGUI::drawExportCommand() { + exitDisabledTimer=1; + + 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 (binary)")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Export (text)")) { + openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); + ImGui::CloseCurrentPopup(); + } +} + +void FurnaceGUI::drawExport() { ImVec2 avail=ImGui::GetContentRegionAvail(); avail.y-=ImGui::GetFrameHeightWithSpacing(); if (ImGui::BeginChild("sysPickerC",avail,false,ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) { if (ImGui::BeginTabBar("ExportTypes")) { if (ImGui::BeginTabItem("Audio")) { - ImGui::RadioButton("one file",&audioExportType,0); - ImGui::RadioButton("multiple files (one per chip)",&audioExportType,1); - ImGui::RadioButton("multiple files (one per channel)",&audioExportType,2); - if (ImGui::InputInt("Loops",&exportLoops,1,2)) { - if (exportLoops<0) exportLoops=0; - } - if (ImGui::InputDouble("Fade out (seconds)",&exportFadeOut,1.0,2.0,"%.1f")) { - if (exportFadeOut<0.0) exportFadeOut=0.0; - } - - if (ImGui::Button("Export")) { - switch (audioExportType) { - case 0: - openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); - break; - case 1: - openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); - break; - case 2: - openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL); - break; - } - ImGui::CloseCurrentPopup(); - } + drawExportAudio(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("VGM")) { - ImGui::Text("settings:"); - if (ImGui::BeginCombo("format version",fmt::sprintf("%d.%.2x",vgmExportVersion>>8,vgmExportVersion&0xff).c_str())) { - for (int i=0; i<7; i++) { - if (ImGui::Selectable(fmt::sprintf("%d.%.2x",vgmVersions[i]>>8,vgmVersions[i]&0xff).c_str(),vgmExportVersion==vgmVersions[i])) { - vgmExportVersion=vgmVersions[i]; - } - } - ImGui::EndCombo(); - } - ImGui::Checkbox("loop",&vgmExportLoop); - if (vgmExportLoop && e->song.loopModality==2) { - ImGui::Text("loop trail:"); - ImGui::Indent(); - if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) { - vgmExportTrailingTicks=-1; - } - if (ImGui::RadioButton("add one loop",vgmExportTrailingTicks==-2)) { - vgmExportTrailingTicks=-2; - } - if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) { - vgmExportTrailingTicks=0; - } - if (vgmExportTrailingTicks>=0) { - ImGui::SameLine(); - if (ImGui::InputInt("##TrailTicks",&vgmExportTrailingTicks,1,100)) { - if (vgmExportTrailingTicks<0) vgmExportTrailingTicks=0; - } - } - ImGui::Unindent(); - } - ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip( - "inserts data blocks on pattern changes.\n" - "useful if you are writing a playback routine.\n\n" - - "the format of a pattern change data block is:\n" - "67 66 FE ll ll ll ll 01 oo rr pp pp pp ...\n" - "- ll: length, a 32-bit little-endian number\n" - "- oo: order\n" - "- rr: initial row (a 0Dxx effect is able to select a different row)\n" - "- pp: pattern index (one per channel)\n\n" - - "pattern indexes are ordered as they appear in the song." - ); - } - ImGui::Checkbox("direct stream mode",&vgmExportDirectStream); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip( - "required for DualPCM and MSM6258 export.\n\n" - "allows for volume/direction changes when playing samples,\n" - "at the cost of a massive increase in file size." - ); - } - ImGui::Text("chips to export:"); - bool hasOneAtLeast=false; - for (int i=0; isong.systemLen; i++) { - int minVersion=e->minVGMVersion(e->song.system[i]); - ImGui::BeginDisabled(minVersion>vgmExportVersion || minVersion==0); - ImGui::Checkbox(fmt::sprintf("%d. %s##_SYSV%d",i+1,getSystemName(e->song.system[i]),i).c_str(),&willExport[i]); - ImGui::EndDisabled(); - if (minVersion>vgmExportVersion) { - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetTooltip("this chip is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); - } - } else if (minVersion==0) { - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetTooltip("this chip is not supported by the VGM format!"); - } - } else { - if (willExport[i]) hasOneAtLeast=true; - } - } - ImGui::Text("select the chip you wish to export, but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); - if (hasOneAtLeast) { - if (ImGui::Button("Export")) { - openFileDialog(GUI_FILE_EXPORT_VGM); - ImGui::CloseCurrentPopup(); - } - } else { - ImGui::Text("nothing to export"); - } + drawExportVGM(); ImGui::EndTabItem(); } int numZSMCompat=0; for (int i=0; isong.systemLen; i++) { - if ((e->song.system[i] == DIV_SYSTEM_VERA) || (e->song.system[i] == DIV_SYSTEM_YM2151)) numZSMCompat++; + if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++; } - if (numZSMCompat > 0) { + if (numZSMCompat>0) { if (ImGui::BeginTabItem("ZSM")) { - ImGui::Text("Commander X16 Zsound Music File"); - if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) { - if (zsmExportTickRate<1) zsmExportTickRate=1; - if (zsmExportTickRate>44100) zsmExportTickRate=44100; - } - ImGui::Checkbox("loop",&zsmExportLoop); - ImGui::SameLine(); - ImGui::Checkbox("optimize size",&zsmExportOptimize); - if (ImGui::Button("Export")) { - openFileDialog(GUI_FILE_EXPORT_ZSM); - ImGui::CloseCurrentPopup(); - } + drawExportZSM(); ImGui::EndTabItem(); } } @@ -169,62 +249,16 @@ void FurnaceGUI::drawExport() { } if (numAmiga && settings.iCannotWait) { if (ImGui::BeginTabItem("Amiga Validation")) { - ImGui::Text( - "this is NOT ROM export! only use for making sure the\n" - "Furnace Amiga emulator is working properly by\n" - "comparing it with real Amiga output." - ); - ImGui::AlignTextToFramePadding(); - ImGui::Text("Directory"); - ImGui::SameLine(); - ImGui::InputText("##AVDPath",&workingDirROMExport); - if (ImGui::Button("Bake Data")) { - std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); - if (workingDirROMExport.size()>0) { - if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; - } - for (DivROMExportOutput& i: out) { - String path=workingDirROMExport+i.name; - FILE* outFile=ps_fopen(path.c_str(),"wb"); - if (outFile!=NULL) { - fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); - fclose(outFile); - } - i.data->finish(); - delete i.data; - } - showError(fmt::sprintf("Done! Baked %d files.",(int)out.size())); - ImGui::CloseCurrentPopup(); - } + drawExportAmigaVal(); ImGui::EndTabItem(); } } if (ImGui::BeginTabItem("Text")) { - ImGui::Text( - "this option exports the song to a text file.\n" - ); - if (ImGui::Button("Export")) { - openFileDialog(GUI_FILE_EXPORT_TEXT); - ImGui::CloseCurrentPopup(); - } + drawExportText(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("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 (binary)")) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); - ImGui::CloseCurrentPopup(); - } - if (ImGui::Button("Export (text)")) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); - ImGui::CloseCurrentPopup(); - } + drawExportCommand(); ImGui::EndTabItem(); } ImGui::EndTabBar(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index dc3c61d91..98e824f35 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4103,130 +4103,20 @@ bool FurnaceGUI::loop() { ImGui::Separator(); if (settings.classicExportOptions) { if (ImGui::BeginMenu("export audio...")) { - exitDisabledTimer=1; - if (ImGui::MenuItem("one file")) { - openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); - } - if (ImGui::MenuItem("multiple files (one per chip)")) { - openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); - } - if (ImGui::MenuItem("multiple files (one per channel)")) { - openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL); - } - if (ImGui::InputInt("Loops",&exportLoops,1,2)) { - if (exportLoops<0) exportLoops=0; - } - if (ImGui::InputDouble("Fade out (seconds)",&exportFadeOut,1.0,2.0,"%.1f")) { - if (exportFadeOut<0.0) exportFadeOut=0.0; - } + drawExportAudio(); ImGui::EndMenu(); } if (ImGui::BeginMenu("export VGM...")) { - exitDisabledTimer=1; - ImGui::Text("settings:"); - if (ImGui::BeginCombo("format version",fmt::sprintf("%d.%.2x",vgmExportVersion>>8,vgmExportVersion&0xff).c_str())) { - for (int i=0; i<7; i++) { - if (ImGui::Selectable(fmt::sprintf("%d.%.2x",vgmVersions[i]>>8,vgmVersions[i]&0xff).c_str(),vgmExportVersion==vgmVersions[i])) { - vgmExportVersion=vgmVersions[i]; - } - } - ImGui::EndCombo(); - } - ImGui::Checkbox("loop",&vgmExportLoop); - if (vgmExportLoop && e->song.loopModality==2) { - ImGui::Text("loop trail:"); - ImGui::Indent(); - if (ImGui::RadioButton("auto-detect",vgmExportTrailingTicks==-1)) { - vgmExportTrailingTicks=-1; - } - if (ImGui::RadioButton("add one loop",vgmExportTrailingTicks==-2)) { - vgmExportTrailingTicks=-2; - } - if (ImGui::RadioButton("custom",vgmExportTrailingTicks>=0)) { - vgmExportTrailingTicks=0; - } - if (vgmExportTrailingTicks>=0) { - ImGui::SameLine(); - if (ImGui::InputInt("##TrailTicks",&vgmExportTrailingTicks,1,100)) { - if (vgmExportTrailingTicks<0) vgmExportTrailingTicks=0; - } - } - ImGui::Unindent(); - } - ImGui::Checkbox("add pattern change hints",&vgmExportPatternHints); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip( - "inserts data blocks on pattern changes.\n" - "useful if you are writing a playback routine.\n\n" - - "the format of a pattern change data block is:\n" - "67 66 FE ll ll ll ll 01 oo rr pp pp pp ...\n" - "- ll: length, a 32-bit little-endian number\n" - "- oo: order\n" - "- rr: initial row (a 0Dxx effect is able to select a different row)\n" - "- pp: pattern index (one per channel)\n\n" - - "pattern indexes are ordered as they appear in the song." - ); - } - ImGui::Checkbox("direct stream mode",&vgmExportDirectStream); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip( - "required for DualPCM and MSM6258 export.\n\n" - "allows for volume/direction changes when playing samples,\n" - "at the cost of a massive increase in file size." - ); - } - ImGui::Text("chips to export:"); - bool hasOneAtLeast=false; - for (int i=0; isong.systemLen; i++) { - int minVersion=e->minVGMVersion(e->song.system[i]); - ImGui::BeginDisabled(minVersion>vgmExportVersion || minVersion==0); - ImGui::Checkbox(fmt::sprintf("%d. %s##_SYSV%d",i+1,getSystemName(e->song.system[i]),i).c_str(),&willExport[i]); - ImGui::EndDisabled(); - if (minVersion>vgmExportVersion) { - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetTooltip("this chip is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); - } - } else if (minVersion==0) { - if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { - ImGui::SetTooltip("this chip is not supported by the VGM format!"); - } - } else { - if (willExport[i]) hasOneAtLeast=true; - } - } - ImGui::Text("select the chip you wish to export,"); - ImGui::Text("but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); - if (hasOneAtLeast) { - if (ImGui::MenuItem("click to export")) { - openFileDialog(GUI_FILE_EXPORT_VGM); - } - } else { - ImGui::Text("nothing to export"); - } + drawExportVGM(); ImGui::EndMenu(); } int numZSMCompat=0; for (int i=0; isong.systemLen; i++) { - if ((e->song.system[i] == DIV_SYSTEM_VERA) || (e->song.system[i] == DIV_SYSTEM_YM2151)) numZSMCompat++; + if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++; } if (numZSMCompat > 0) { if (ImGui::BeginMenu("export ZSM...")) { - exitDisabledTimer=1; - ImGui::Text("Commander X16 Zsound Music File"); - if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,10)) { - if (zsmExportTickRate<1) zsmExportTickRate=1; - if (zsmExportTickRate>44100) zsmExportTickRate=44100; - } - ImGui::Checkbox("loop",&zsmExportLoop); - ImGui::SameLine(); - ImGui::Checkbox("optimize size",&zsmExportOptimize); - ImGui::SameLine(); - if (ImGui::Button("Begin Export")) { - openFileDialog(GUI_FILE_EXPORT_ZSM); - ImGui::CloseCurrentPopup(); - } + drawExportZSM(); ImGui::EndMenu(); } } @@ -4236,62 +4126,16 @@ bool FurnaceGUI::loop() { } if (numAmiga && settings.iCannotWait) { if (ImGui::BeginMenu("export Amiga validation data...")) { - exitDisabledTimer=1; - ImGui::Text( - "this is NOT ROM export! only use for making sure the\n" - "Furnace Amiga emulator is working properly by\n" - "comparing it with real Amiga output." - ); - ImGui::AlignTextToFramePadding(); - ImGui::Text("Directory"); - ImGui::SameLine(); - ImGui::InputText("##AVDPath",&workingDirROMExport); - if (ImGui::Button("Bake Data")) { - std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); - if (workingDirROMExport.size()>0) { - if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; - } - for (DivROMExportOutput& i: out) { - String path=workingDirROMExport+i.name; - FILE* outFile=ps_fopen(path.c_str(),"wb"); - if (outFile!=NULL) { - fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); - fclose(outFile); - } - i.data->finish(); - delete i.data; - } - showError(fmt::sprintf("Done! Baked %d files.",(int)out.size())); - ImGui::CloseCurrentPopup(); - } + drawExportAmigaVal(); ImGui::EndMenu(); } } if (ImGui::BeginMenu("export text...")) { - exitDisabledTimer=1; - ImGui::Text( - "this option exports the song to a text file.\n" - ); - if (ImGui::Button("export")) { - openFileDialog(GUI_FILE_EXPORT_TEXT); - } + drawExportText(); ImGui::EndMenu(); } if (ImGui::BeginMenu("export command stream...")) { - exitDisabledTimer=1; - 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 (binary)")) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); - } - if (ImGui::Button("export (text)")) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); - } + drawExportCommand(); ImGui::EndMenu(); } } else { @@ -7750,7 +7594,6 @@ FurnaceGUI::FurnaceGUI(): introStopped(false), curTutorial(-1), curTutorialStep(0), - //audio export types (export options) audioExportType(0) { // value keys valueKeys[SDLK_0]=0; diff --git a/src/gui/gui.h b/src/gui/gui.h index 63c61551a..d709cb048 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2334,9 +2334,16 @@ class FurnaceGUI { // tutorial int curTutorial, curTutorialStep; - //audio export types (export options) + // audio export types (export options) int audioExportType; + void drawExportAudio(); + void drawExportVGM(); + void drawExportZSM(); + void drawExportAmigaVal(); + void drawExportText(); + void drawExportCommand(); + void drawSSGEnv(unsigned char type, const ImVec2& size); void drawWaveform(unsigned char type, bool opz, const ImVec2& size); void drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size); From 5697330c04b6ee59bcc013fa88fd301fac4b9827 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 21 Dec 2023 20:08:15 -0500 Subject: [PATCH 28/53] i won't call it classic --- src/gui/gui.cpp | 2 +- src/gui/gui.h | 4 ++-- src/gui/settings.cpp | 26 +++++++++++++++++--------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 98e824f35..8ab1abd9b 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4101,7 +4101,7 @@ bool FurnaceGUI::loop() { openFileDialog(GUI_FILE_SAVE_DMF_LEGACY); } ImGui::Separator(); - if (settings.classicExportOptions) { + if (settings.exportOptionsLayout) { if (ImGui::BeginMenu("export audio...")) { drawExportAudio(); ImGui::EndMenu(); diff --git a/src/gui/gui.h b/src/gui/gui.h index d709cb048..9b35b4f23 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1689,7 +1689,7 @@ class FurnaceGUI { int centerPopup; int insIconsStyle; int classicChipOptions; - int classicExportOptions; + int exportOptionsLayout; int wasapiEx; int chanOscThreads; int renderPoolThreads; @@ -1886,7 +1886,7 @@ class FurnaceGUI { centerPopup(1), insIconsStyle(1), classicChipOptions(0), - classicExportOptions(0), // poll? + exportOptionsLayout(1), wasapiEx(0), chanOscThreads(0), renderPoolThreads(0), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 86b1ed964..8cc0d6b73 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -2660,6 +2660,20 @@ void FurnaceGUI::drawSettings() { } ImGui::Unindent(); + ImGui::Text("Export options layout:"); + if (ImGui::RadioButton("Sub-menus in File menu##eol0",settings.exportOptionsLayout==0)) { + settings.exportOptionsLayout=0; + settingsChanged=true; + } + if (ImGui::RadioButton("Modal window with tabs##eol1",settings.exportOptionsLayout==1)) { + settings.exportOptionsLayout=1; + settingsChanged=true; + } + if (ImGui::RadioButton("Modal windows with options in File menu##eol2",settings.exportOptionsLayout==2)) { + settings.exportOptionsLayout=2; + settingsChanged=true; + } + bool capitalMenuBarB=settings.capitalMenuBar; if (ImGui::Checkbox("Capitalize menu bar",&capitalMenuBarB)) { settings.capitalMenuBar=capitalMenuBarB; @@ -2672,12 +2686,6 @@ void FurnaceGUI::drawSettings() { settingsChanged=true; } - bool classicExportOptionsB=settings.classicExportOptions; - if (ImGui::Checkbox("Display separate export options in File menu",&classicExportOptionsB)) { - settings.classicExportOptions=classicExportOptionsB; - settingsChanged=true; - } - // SUBSECTION ORDERS CONFIG_SUBSECTION("Orders"); // sorry. temporarily disabled until ImGui has a way to add separators in tables arbitrarily. @@ -3873,7 +3881,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { settings.centerPopup=conf.getInt("centerPopup",1); settings.insIconsStyle=conf.getInt("insIconsStyle",1); settings.classicChipOptions=conf.getInt("classicChipOptions",0); - settings.classicExportOptions=conf.getInt("classicExportOptions",0); + settings.exportOptionsLayout=conf.getInt("exportOptionsLayout",1); settings.wasapiEx=conf.getInt("wasapiEx",0); settings.chanOscThreads=conf.getInt("chanOscThreads",0); settings.renderPoolThreads=conf.getInt("renderPoolThreads",0); @@ -4042,7 +4050,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { clampSetting(settings.centerPopup,0,1); clampSetting(settings.insIconsStyle,0,2); clampSetting(settings.classicChipOptions,0,1); - clampSetting(settings.classicExportOptions,0,1); + clampSetting(settings.exportOptionsLayout,0,2); clampSetting(settings.wasapiEx,0,1); clampSetting(settings.chanOscThreads,0,256); clampSetting(settings.renderPoolThreads,0,DIV_MAX_CHIPS); @@ -4292,7 +4300,7 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("centerPopup",settings.centerPopup); conf.set("insIconsStyle",settings.insIconsStyle); conf.set("classicChipOptions",settings.classicChipOptions); - conf.set("classicExportOptions",settings.classicExportOptions); + conf.set("exportOptionsLayout",settings.exportOptionsLayout); conf.set("wasapiEx",settings.wasapiEx); conf.set("chanOscThreads",settings.chanOscThreads); conf.set("renderPoolThreads",settings.renderPoolThreads); From f8b0c556cac0cd5303c70e837a8ee5a3901191c3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 22 Dec 2023 00:14:52 -0500 Subject: [PATCH 29/53] Namco WSG: fix vol macro scaling --- src/engine/platform/namcowsg.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/namcowsg.cpp b/src/engine/platform/namcowsg.cpp index 0aa4bbd94..223216f66 100644 --- a/src/engine/platform/namcowsg.cpp +++ b/src/engine/platform/namcowsg.cpp @@ -199,7 +199,7 @@ void DivPlatformNamcoWSG::tick(bool sysTick) { for (int i=0; i>4; + chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol,chan[i].std.vol.val,15); } if (chan[i].std.duty.had) { chan[i].noise=chan[i].std.duty.val; From f80340ebccd7aa0af444c371f547c48cf49817c1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 22 Dec 2023 15:43:41 -0500 Subject: [PATCH 30/53] more work --- src/gui/doAction.cpp | 1 + src/gui/editControls.cpp | 20 ++------------------ src/gui/exportOptions.cpp | 2 +- src/gui/gui.cpp | 9 ++++++--- src/gui/gui.h | 3 ++- src/gui/guiConst.cpp | 2 +- src/gui/settings.cpp | 2 ++ 7 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 73d3876e7..645c684d2 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -66,6 +66,7 @@ void FurnaceGUI::doAction(int what) { openFileDialog(GUI_FILE_SAVE); break; case GUI_ACTION_EXPORT: + curExportType=-1; displayExport=true; break; case GUI_ACTION_UNDO: diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index b9b8a7409..1ce85020d 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -519,24 +519,8 @@ void FurnaceGUI::drawMobileControls() { openFileDialog(GUI_FILE_SAVE_DMF_LEGACY); } ImGui::SameLine(); - if (ImGui::Button("Export Audio")) { - openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); - } - ImGui::SameLine(); - if (ImGui::Button("Export VGM")) { - openFileDialog(GUI_FILE_EXPORT_VGM); - } - - if (ImGui::Button("CmdStream")) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); - } - ImGui::SameLine(); - if (ImGui::Button("CmdStream Text")) { - openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); - } - ImGui::SameLine(); - if (ImGui::Button("Text")) { - openFileDialog(GUI_FILE_EXPORT_TEXT); + if (ImGui::Button("Export")) { + doAction(GUI_ACTION_EXPORT); } if (ImGui::Button("Restore Backup")) { diff --git a/src/gui/exportOptions.cpp b/src/gui/exportOptions.cpp index d5765fe2b..dc0af0c28 100644 --- a/src/gui/exportOptions.cpp +++ b/src/gui/exportOptions.cpp @@ -223,7 +223,7 @@ void FurnaceGUI::drawExport() { ImVec2 avail=ImGui::GetContentRegionAvail(); avail.y-=ImGui::GetFrameHeightWithSpacing(); - if (ImGui::BeginChild("sysPickerC",avail,false,ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) { + if (ImGui::BeginChild("sysPickerC",avail,false)) { if (ImGui::BeginTabBar("ExportTypes")) { if (ImGui::BeginTabItem("Audio")) { drawExportAudio(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 8ab1abd9b..5a7d77fca 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4101,7 +4101,7 @@ bool FurnaceGUI::loop() { openFileDialog(GUI_FILE_SAVE_DMF_LEGACY); } ImGui::Separator(); - if (settings.exportOptionsLayout) { + if (settings.exportOptionsLayout==0) { if (ImGui::BeginMenu("export audio...")) { drawExportAudio(); ImGui::EndMenu(); @@ -4114,7 +4114,7 @@ bool FurnaceGUI::loop() { for (int i=0; isong.systemLen; i++) { if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++; } - if (numZSMCompat > 0) { + if (numZSMCompat>0) { if (ImGui::BeginMenu("export ZSM...")) { drawExportZSM(); ImGui::EndMenu(); @@ -4138,6 +4138,8 @@ bool FurnaceGUI::loop() { drawExportCommand(); ImGui::EndMenu(); } + } else if (settings.exportOptionsLayout==2) { + } else { if (ImGui::MenuItem("export...",BIND_FOR(GUI_ACTION_EXPORT))) { displayExport=true; @@ -7594,7 +7596,8 @@ FurnaceGUI::FurnaceGUI(): introStopped(false), curTutorial(-1), curTutorialStep(0), - audioExportType(0) { + audioExportType(0), + curExportType(-1) { // value keys valueKeys[SDLK_0]=0; valueKeys[SDLK_1]=1; diff --git a/src/gui/gui.h b/src/gui/gui.h index 9b35b4f23..7e1c26379 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2334,8 +2334,9 @@ class FurnaceGUI { // tutorial int curTutorial, curTutorialStep; - // audio export types (export options) + // export options int audioExportType; + int curExportType; void drawExportAudio(); void drawExportVGM(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index b3824e360..b2417761b 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -537,7 +537,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("OPEN_BACKUP", "Restore backup", 0), D("SAVE", "Save file", FURKMOD_CMD|SDLK_s), D("SAVE_AS", "Save as", FURKMOD_CMD|FURKMOD_SHIFT|SDLK_s), - D("EXPORT", "Export", FURKMOD_CMD|SDLK_e), + D("EXPORT", "Export", 0), D("UNDO", "Undo", FURKMOD_CMD|SDLK_z), #ifdef __APPLE__ D("REDO", "Redo", FURKMOD_CMD|FURKMOD_SHIFT|SDLK_z), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 8cc0d6b73..86fdcd826 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -2661,6 +2661,7 @@ void FurnaceGUI::drawSettings() { ImGui::Unindent(); ImGui::Text("Export options layout:"); + ImGui::Indent(); if (ImGui::RadioButton("Sub-menus in File menu##eol0",settings.exportOptionsLayout==0)) { settings.exportOptionsLayout=0; settingsChanged=true; @@ -2673,6 +2674,7 @@ void FurnaceGUI::drawSettings() { settings.exportOptionsLayout=2; settingsChanged=true; } + ImGui::Unindent(); bool capitalMenuBarB=settings.capitalMenuBar; if (ImGui::Checkbox("Capitalize menu bar",&capitalMenuBarB)) { From 15f0f50deff4a4890cb1affc0ed56726f53567da Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 22 Dec 2023 18:23:11 -0500 Subject: [PATCH 31/53] part 2 of more work --- src/gui/doAction.cpp | 2 +- src/gui/gui.cpp | 39 +++++++++++++++++++++++++++++++++++++-- src/gui/gui.h | 13 ++++++++++++- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 645c684d2..c04f14118 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -66,7 +66,7 @@ void FurnaceGUI::doAction(int what) { openFileDialog(GUI_FILE_SAVE); break; case GUI_ACTION_EXPORT: - curExportType=-1; + curExportType=GUI_EXPORT_NONE; displayExport=true; break; case GUI_ACTION_UNDO: diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 5a7d77fca..e74d33807 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4139,7 +4139,42 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } } else if (settings.exportOptionsLayout==2) { - + if (ImGui::MenuItem("export audio...")) { + curExportType=GUI_EXPORT_AUDIO; + displayExport=true; + } + if (ImGui::MenuItem("export VGM...")) { + curExportType=GUI_EXPORT_VGM; + displayExport=true; + } + int numZSMCompat=0; + for (int i=0; isong.systemLen; i++) { + if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++; + } + if (numZSMCompat>0) { + if (ImGui::MenuItem("export ZSM...")) { + curExportType=GUI_EXPORT_ZSM; + displayExport=true; + } + } + int numAmiga=0; + for (int i=0; isong.systemLen; i++) { + if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; + } + if (numAmiga && settings.iCannotWait) { + if (ImGui::MenuItem("export Amiga validation data...")) { + curExportType=GUI_EXPORT_AMIGA_VAL; + displayExport=true; + } + } + if (ImGui::MenuItem("export text...")) { + curExportType=GUI_EXPORT_TEXT; + displayExport=true; + } + if (ImGui::MenuItem("export command stream...")) { + curExportType=GUI_EXPORT_CMD_STREAM; + displayExport=true; + } } else { if (ImGui::MenuItem("export...",BIND_FOR(GUI_ACTION_EXPORT))) { displayExport=true; @@ -7597,7 +7632,7 @@ FurnaceGUI::FurnaceGUI(): curTutorial(-1), curTutorialStep(0), audioExportType(0), - curExportType(-1) { + curExportType(GUI_EXPORT_NONE) { // value keys valueKeys[SDLK_0]=0; valueKeys[SDLK_1]=1; diff --git a/src/gui/gui.h b/src/gui/gui.h index 7e1c26379..f1aa1915e 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -549,6 +549,17 @@ enum FurnaceGUIWarnings { GUI_WARN_GENERIC }; +enum FurnaceGUIExportTypes { + GUI_EXPORT_NONE=-1, + + GUI_EXPORT_AUDIO=0, + GUI_EXPORT_VGM, + GUI_EXPORT_ZSM, + GUI_EXPORT_CMD_STREAM, + GUI_EXPORT_AMIGA_VAL, + GUI_EXPORT_TEXT +}; + enum FurnaceGUIFMAlgs { FM_ALGS_4OP, FM_ALGS_2OP_OPL, @@ -2336,7 +2347,7 @@ class FurnaceGUI { // export options int audioExportType; - int curExportType; + FurnaceGUIExportTypes curExportType; void drawExportAudio(); void drawExportVGM(); From 3f92cc801322f2b2a1d79319b12f828ffc19d6de Mon Sep 17 00:00:00 2001 From: LTVA1 <87536432+LTVA1@users.noreply.github.com> Date: Fri, 22 Dec 2023 23:08:20 +0300 Subject: [PATCH 32/53] correct bug in OpenMPT paste --- src/gui/editing.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index d27c55a13..22e0249f5 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -771,10 +771,10 @@ unsigned int convertEffectMPT_S3M(unsigned char symbol, unsigned int val) { return (0x80<<8)|((val&0xf)<<4); break; case 0xC: - return (0xFC<<8)|(val&0xf); + return (0xEC<<8)|(val&0xf); break; case 0xD: - return (0xFD<<8)|(val&0xf); + return (0xED<<8)|(val&0xf); break; default: break; From f7d325d4d71df51bd347f6998a11627c542b0d69 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 22 Dec 2023 18:39:00 -0500 Subject: [PATCH 33/53] fix crash when making the export window too small --- src/gui/exportOptions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/exportOptions.cpp b/src/gui/exportOptions.cpp index dc0af0c28..04fe51966 100644 --- a/src/gui/exportOptions.cpp +++ b/src/gui/exportOptions.cpp @@ -263,8 +263,8 @@ void FurnaceGUI::drawExport() { } ImGui::EndTabBar(); } - ImGui::EndChild(); } + ImGui::EndChild(); ImGui::Separator(); if (ImGui::Button("Cancel")) ImGui::CloseCurrentPopup(); } From c65debad68d0ae8e42f28ef94ab837877e2d4725 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 22 Dec 2023 18:56:02 -0500 Subject: [PATCH 34/53] GUI: implement separate export options --- src/gui/exportOptions.cpp | 126 ++++++++++++++++++++++++++++---------- 1 file changed, 92 insertions(+), 34 deletions(-) diff --git a/src/gui/exportOptions.cpp b/src/gui/exportOptions.cpp index 04fe51966..b4e99dcf8 100644 --- a/src/gui/exportOptions.cpp +++ b/src/gui/exportOptions.cpp @@ -224,44 +224,102 @@ void FurnaceGUI::drawExport() { avail.y-=ImGui::GetFrameHeightWithSpacing(); if (ImGui::BeginChild("sysPickerC",avail,false)) { - if (ImGui::BeginTabBar("ExportTypes")) { - if (ImGui::BeginTabItem("Audio")) { + if (settings.exportOptionsLayout==1 || curExportType==GUI_EXPORT_NONE) { + if (ImGui::BeginTabBar("ExportTypes")) { + if (ImGui::BeginTabItem("Audio")) { + drawExportAudio(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("VGM")) { + drawExportVGM(); + ImGui::EndTabItem(); + } + int numZSMCompat=0; + for (int i=0; isong.systemLen; i++) { + if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++; + } + if (numZSMCompat>0) { + if (ImGui::BeginTabItem("ZSM")) { + drawExportZSM(); + ImGui::EndTabItem(); + } + } + int numAmiga=0; + for (int i=0; isong.systemLen; i++) { + if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; + } + if (numAmiga && settings.iCannotWait) { + if (ImGui::BeginTabItem("Amiga Validation")) { + drawExportAmigaVal(); + ImGui::EndTabItem(); + } + } + if (ImGui::BeginTabItem("Text")) { + drawExportText(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Command Stream")) { + drawExportCommand(); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + } else switch (curExportType) { + case GUI_EXPORT_AUDIO: drawExportAudio(); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("VGM")) { + break; + case GUI_EXPORT_VGM: drawExportVGM(); - ImGui::EndTabItem(); - } - int numZSMCompat=0; - for (int i=0; isong.systemLen; i++) { - if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++; - } - if (numZSMCompat>0) { - if (ImGui::BeginTabItem("ZSM")) { - drawExportZSM(); - ImGui::EndTabItem(); - } - } - int numAmiga=0; - for (int i=0; isong.systemLen; i++) { - if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; - } - if (numAmiga && settings.iCannotWait) { - if (ImGui::BeginTabItem("Amiga Validation")) { - drawExportAmigaVal(); - ImGui::EndTabItem(); - } - } - if (ImGui::BeginTabItem("Text")) { + break; + case GUI_EXPORT_ZSM: + drawExportZSM(); + break; + case GUI_EXPORT_AMIGA_VAL: + drawExportAmigaVal(); + break; + case GUI_EXPORT_TEXT: drawExportText(); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Command Stream")) { + break; + case GUI_EXPORT_CMD_STREAM: drawExportCommand(); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); + break; + default: + ImGui::Text("congratulations! you've unlocked a secret panel."); + if (ImGui::Button("Toggle hidden systems")) { + settings.hiddenSystems=!settings.hiddenSystems; + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Toggle all instrument types")) { + settings.displayAllInsTypes=!settings.displayAllInsTypes; + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Set pitch linearity to Partial")) { + e->song.linearPitch=1; + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Enable multi-threading settings")) { + settings.showPool=1; + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Set fat to max")) { + ImGuiStyle& sty=ImGui::GetStyle(); + sty.FramePadding=ImVec2(20.0f*dpiScale,20.0f*dpiScale); + sty.ItemSpacing=ImVec2(10.0f*dpiScale,10.0f*dpiScale); + sty.ItemInnerSpacing=ImVec2(10.0f*dpiScale,10.0f*dpiScale); + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Set muscle and fat to zero")) { + ImGuiStyle& sty=ImGui::GetStyle(); + sty.FramePadding=ImVec2(0,0); + sty.ItemSpacing=ImVec2(0,0); + sty.ItemInnerSpacing=ImVec2(0,0); + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Tell tildearrow this must be a mistake")) { + showError("yeah, it's a bug. write a bug report in the GitHub page and tell me how did you get here."); + ImGui::CloseCurrentPopup(); + } + break; } } ImGui::EndChild(); From 27f4f6830e15dce234a04f17564e15d99be639ca Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 22 Dec 2023 19:18:23 -0500 Subject: [PATCH 35/53] GUI: improve the buttons --- src/gui/exportOptions.cpp | 255 +++++++++++++++++++++----------------- src/gui/gui.cpp | 5 +- src/gui/gui.h | 14 +-- 3 files changed, 150 insertions(+), 124 deletions(-) diff --git a/src/gui/exportOptions.cpp b/src/gui/exportOptions.cpp index b4e99dcf8..165ba5f95 100644 --- a/src/gui/exportOptions.cpp +++ b/src/gui/exportOptions.cpp @@ -21,8 +21,9 @@ #include "guiConst.h" #include "../fileutils.h" #include "misc/cpp/imgui_stdlib.h" +#include -void FurnaceGUI::drawExportAudio() { +void FurnaceGUI::drawExportAudio(bool onWindow) { exitDisabledTimer=1; ImGui::RadioButton("one file",&audioExportType,0); @@ -35,7 +36,13 @@ void FurnaceGUI::drawExportAudio() { if (exportFadeOut<0.0) exportFadeOut=0.0; } - if (ImGui::Button("Export")) { + if (onWindow) { + ImGui::Separator(); + if (ImGui::Button("Cancel",ImVec2(200.0f*dpiScale,0))) ImGui::CloseCurrentPopup(); + ImGui::SameLine(); + } + + if (ImGui::Button("Export",ImVec2(200.0f*dpiScale,0))) { switch (audioExportType) { case 0: openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); @@ -51,7 +58,7 @@ void FurnaceGUI::drawExportAudio() { } } -void FurnaceGUI::drawExportVGM() { +void FurnaceGUI::drawExportVGM(bool onWindow) { exitDisabledTimer=1; ImGui::Text("settings:"); @@ -129,16 +136,25 @@ void FurnaceGUI::drawExportVGM() { } ImGui::Text("select the chip you wish to export, but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); if (hasOneAtLeast) { - if (ImGui::Button("Export")) { + if (onWindow) { + ImGui::Separator(); + if (ImGui::Button("Cancel",ImVec2(200.0f*dpiScale,0))) ImGui::CloseCurrentPopup(); + ImGui::SameLine(); + } + if (ImGui::Button("Export",ImVec2(200.0f*dpiScale,0))) { openFileDialog(GUI_FILE_EXPORT_VGM); ImGui::CloseCurrentPopup(); } } else { ImGui::Text("nothing to export"); + if (onWindow) { + ImGui::Separator(); + if (ImGui::Button("Cancel",ImVec2(400.0f*dpiScale,0))) ImGui::CloseCurrentPopup(); + } } } -void FurnaceGUI::drawExportZSM() { +void FurnaceGUI::drawExportZSM(bool onWindow) { exitDisabledTimer=1; ImGui::Text("Commander X16 Zsound Music File"); @@ -149,13 +165,18 @@ void FurnaceGUI::drawExportZSM() { ImGui::Checkbox("loop",&zsmExportLoop); ImGui::SameLine(); ImGui::Checkbox("optimize size",&zsmExportOptimize); - if (ImGui::Button("Export")) { + if (onWindow) { + ImGui::Separator(); + if (ImGui::Button("Cancel",ImVec2(200.0f*dpiScale,0))) ImGui::CloseCurrentPopup(); + ImGui::SameLine(); + } + if (ImGui::Button("Export",ImVec2(200.0f*dpiScale,0))) { openFileDialog(GUI_FILE_EXPORT_ZSM); ImGui::CloseCurrentPopup(); } } -void FurnaceGUI::drawExportAmigaVal() { +void FurnaceGUI::drawExportAmigaVal(bool onWindow) { exitDisabledTimer=1; ImGui::Text( @@ -167,7 +188,12 @@ void FurnaceGUI::drawExportAmigaVal() { ImGui::Text("Directory"); ImGui::SameLine(); ImGui::InputText("##AVDPath",&workingDirROMExport); - if (ImGui::Button("Bake Data")) { + if (onWindow) { + ImGui::Separator(); + if (ImGui::Button("Cancel",ImVec2(200.0f*dpiScale,0))) ImGui::CloseCurrentPopup(); + ImGui::SameLine(); + } + if (ImGui::Button("Bake Data",ImVec2(200.0f*dpiScale,0))) { std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); if (workingDirROMExport.size()>0) { if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; @@ -187,19 +213,24 @@ void FurnaceGUI::drawExportAmigaVal() { } } -void FurnaceGUI::drawExportText() { +void FurnaceGUI::drawExportText(bool onWindow) { exitDisabledTimer=1; ImGui::Text( "this option exports the song to a text file.\n" ); - if (ImGui::Button("Export")) { + if (onWindow) { + ImGui::Separator(); + if (ImGui::Button("Cancel",ImVec2(200.0f*dpiScale,0))) ImGui::CloseCurrentPopup(); + ImGui::SameLine(); + } + if (ImGui::Button("Export",ImVec2(200.0f*dpiScale,0))) { openFileDialog(GUI_FILE_EXPORT_TEXT); ImGui::CloseCurrentPopup(); } } -void FurnaceGUI::drawExportCommand() { +void FurnaceGUI::drawExportCommand(bool onWindow) { exitDisabledTimer=1; ImGui::Text( @@ -209,120 +240,118 @@ void FurnaceGUI::drawExportCommand() { "technical/development use only!" ); - if (ImGui::Button("Export (binary)")) { + if (onWindow) { + ImGui::Separator(); + if (ImGui::Button("Cancel",ImVec2(133.3f*dpiScale,0))) ImGui::CloseCurrentPopup(); + ImGui::SameLine(); + } + if (ImGui::Button("Export (binary)",ImVec2(133.3f*dpiScale,0))) { openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY); ImGui::CloseCurrentPopup(); } - if (ImGui::Button("Export (text)")) { + ImGui::SameLine(); + if (ImGui::Button("Export (text)",ImVec2(133.3f*dpiScale,0))) { openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); ImGui::CloseCurrentPopup(); } } void FurnaceGUI::drawExport() { - ImVec2 avail=ImGui::GetContentRegionAvail(); - avail.y-=ImGui::GetFrameHeightWithSpacing(); - - if (ImGui::BeginChild("sysPickerC",avail,false)) { - if (settings.exportOptionsLayout==1 || curExportType==GUI_EXPORT_NONE) { - if (ImGui::BeginTabBar("ExportTypes")) { - if (ImGui::BeginTabItem("Audio")) { - drawExportAudio(); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("VGM")) { - drawExportVGM(); - ImGui::EndTabItem(); - } - int numZSMCompat=0; - for (int i=0; isong.systemLen; i++) { - if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++; - } - if (numZSMCompat>0) { - if (ImGui::BeginTabItem("ZSM")) { - drawExportZSM(); - ImGui::EndTabItem(); - } - } - int numAmiga=0; - for (int i=0; isong.systemLen; i++) { - if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; - } - if (numAmiga && settings.iCannotWait) { - if (ImGui::BeginTabItem("Amiga Validation")) { - drawExportAmigaVal(); - ImGui::EndTabItem(); - } - } - if (ImGui::BeginTabItem("Text")) { - drawExportText(); - ImGui::EndTabItem(); - } - if (ImGui::BeginTabItem("Command Stream")) { - drawExportCommand(); - ImGui::EndTabItem(); - } - ImGui::EndTabBar(); + if (settings.exportOptionsLayout==1 || curExportType==GUI_EXPORT_NONE) { + if (ImGui::BeginTabBar("ExportTypes")) { + if (ImGui::BeginTabItem("Audio")) { + drawExportAudio(true); + ImGui::EndTabItem(); } - } else switch (curExportType) { - case GUI_EXPORT_AUDIO: - drawExportAudio(); - break; - case GUI_EXPORT_VGM: - drawExportVGM(); - break; - case GUI_EXPORT_ZSM: - drawExportZSM(); - break; - case GUI_EXPORT_AMIGA_VAL: - drawExportAmigaVal(); - break; - case GUI_EXPORT_TEXT: - drawExportText(); - break; - case GUI_EXPORT_CMD_STREAM: - drawExportCommand(); - break; - default: - ImGui::Text("congratulations! you've unlocked a secret panel."); - if (ImGui::Button("Toggle hidden systems")) { - settings.hiddenSystems=!settings.hiddenSystems; - ImGui::CloseCurrentPopup(); + if (ImGui::BeginTabItem("VGM")) { + drawExportVGM(true); + ImGui::EndTabItem(); + } + int numZSMCompat=0; + for (int i=0; isong.systemLen; i++) { + if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++; + } + if (numZSMCompat>0) { + if (ImGui::BeginTabItem("ZSM")) { + drawExportZSM(true); + ImGui::EndTabItem(); } - if (ImGui::Button("Toggle all instrument types")) { - settings.displayAllInsTypes=!settings.displayAllInsTypes; - ImGui::CloseCurrentPopup(); + } + int numAmiga=0; + for (int i=0; isong.systemLen; i++) { + if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; + } + if (numAmiga && settings.iCannotWait) { + if (ImGui::BeginTabItem("Amiga Validation")) { + drawExportAmigaVal(true); + ImGui::EndTabItem(); } - if (ImGui::Button("Set pitch linearity to Partial")) { - e->song.linearPitch=1; - ImGui::CloseCurrentPopup(); - } - if (ImGui::Button("Enable multi-threading settings")) { - settings.showPool=1; - ImGui::CloseCurrentPopup(); - } - if (ImGui::Button("Set fat to max")) { - ImGuiStyle& sty=ImGui::GetStyle(); - sty.FramePadding=ImVec2(20.0f*dpiScale,20.0f*dpiScale); - sty.ItemSpacing=ImVec2(10.0f*dpiScale,10.0f*dpiScale); - sty.ItemInnerSpacing=ImVec2(10.0f*dpiScale,10.0f*dpiScale); - ImGui::CloseCurrentPopup(); - } - if (ImGui::Button("Set muscle and fat to zero")) { - ImGuiStyle& sty=ImGui::GetStyle(); - sty.FramePadding=ImVec2(0,0); - sty.ItemSpacing=ImVec2(0,0); - sty.ItemInnerSpacing=ImVec2(0,0); - ImGui::CloseCurrentPopup(); - } - if (ImGui::Button("Tell tildearrow this must be a mistake")) { - showError("yeah, it's a bug. write a bug report in the GitHub page and tell me how did you get here."); - ImGui::CloseCurrentPopup(); - } - break; + } + if (ImGui::BeginTabItem("Text")) { + drawExportText(true); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Command Stream")) { + drawExportCommand(true); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); } + } else switch (curExportType) { + case GUI_EXPORT_AUDIO: + drawExportAudio(true); + break; + case GUI_EXPORT_VGM: + drawExportVGM(true); + break; + case GUI_EXPORT_ZSM: + drawExportZSM(true); + break; + case GUI_EXPORT_AMIGA_VAL: + drawExportAmigaVal(true); + break; + case GUI_EXPORT_TEXT: + drawExportText(true); + break; + case GUI_EXPORT_CMD_STREAM: + drawExportCommand(true); + break; + default: + ImGui::Text("congratulations! you've unlocked a secret panel."); + if (ImGui::Button("Toggle hidden systems")) { + settings.hiddenSystems=!settings.hiddenSystems; + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Toggle all instrument types")) { + settings.displayAllInsTypes=!settings.displayAllInsTypes; + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Set pitch linearity to Partial")) { + e->song.linearPitch=1; + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Enable multi-threading settings")) { + settings.showPool=1; + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Set fat to max")) { + ImGuiStyle& sty=ImGui::GetStyle(); + sty.FramePadding=ImVec2(20.0f*dpiScale,20.0f*dpiScale); + sty.ItemSpacing=ImVec2(10.0f*dpiScale,10.0f*dpiScale); + sty.ItemInnerSpacing=ImVec2(10.0f*dpiScale,10.0f*dpiScale); + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Set muscle and fat to zero")) { + ImGuiStyle& sty=ImGui::GetStyle(); + sty.FramePadding=ImVec2(0,0); + sty.ItemSpacing=ImVec2(0,0); + sty.ItemInnerSpacing=ImVec2(0,0); + ImGui::CloseCurrentPopup(); + } + if (ImGui::Button("Tell tildearrow this must be a mistake")) { + showError("yeah, it's a bug. write a bug report in the GitHub page and tell me how did you get here."); + ImGui::CloseCurrentPopup(); + } + break; } - ImGui::EndChild(); - ImGui::Separator(); - if (ImGui::Button("Cancel")) ImGui::CloseCurrentPopup(); } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e74d33807..7d17af0ee 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5420,11 +5420,8 @@ bool FurnaceGUI::loop() { ImGui::EndPopup(); } - if (ImGui::BeginPopupModal("Export",NULL,ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) { + if (ImGui::BeginPopupModal("Export",NULL,ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::SetWindowPos(ImVec2(((canvasW)-ImGui::GetWindowSize().x)*0.5,((canvasH)-ImGui::GetWindowSize().y)*0.5)); - if (ImGui::GetWindowSize().x Date: Fri, 22 Dec 2023 20:22:53 -0500 Subject: [PATCH 36/53] GUI: now let's put these config options into group s --- src/gui/gui.h | 1 + src/gui/settings.cpp | 222 ++++++++++++++++++++++++++----------------- 2 files changed, 137 insertions(+), 86 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index fed9eea90..1cfbfc011 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -478,6 +478,7 @@ enum FurnaceGUISettingGroups: unsigned int { GUI_SETTINGS_APPEARANCE=64, GUI_SETTINGS_LAYOUTS=128, GUI_SETTINGS_COLOR=256, + GUI_SETTINGS_EMULATION=512, GUI_SETTINGS_ALL=0xffffffff }; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 86fdcd826..b71582c7f 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -4121,51 +4121,141 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { } void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { - conf.set("mainFontSize",settings.mainFontSize); - conf.set("headFontSize",settings.headFontSize); - conf.set("patFontSize",settings.patFontSize); - conf.set("iconSize",settings.iconSize); - conf.set("audioEngine",String(audioBackends[settings.audioEngine])); - conf.set("audioDevice",settings.audioDevice); - conf.set("midiInDevice",settings.midiInDevice); - conf.set("midiOutDevice",settings.midiOutDevice); - conf.set("renderDriver",settings.renderDriver); - conf.set("sdlAudioDriver",settings.sdlAudioDriver); - conf.set("audioQuality",settings.audioQuality); - conf.set("audioHiPass",settings.audioHiPass); - conf.set("audioBufSize",settings.audioBufSize); - conf.set("audioRate",settings.audioRate); - conf.set("audioChans",settings.audioChans); - conf.set("arcadeCore",settings.arcadeCore); - conf.set("ym2612Core",settings.ym2612Core); - conf.set("snCore",settings.snCore); - conf.set("nesCore",settings.nesCore); - conf.set("fdsCore",settings.fdsCore); - conf.set("c64Core",settings.c64Core); - conf.set("pokeyCore",settings.pokeyCore); - conf.set("opnCore",settings.opnCore); - conf.set("opl2Core",settings.opl2Core); - conf.set("opl3Core",settings.opl3Core); - conf.set("arcadeCoreRender",settings.arcadeCoreRender); - conf.set("ym2612CoreRender",settings.ym2612CoreRender); - conf.set("snCoreRender",settings.snCoreRender); - conf.set("nesCoreRender",settings.nesCoreRender); - conf.set("fdsCoreRender",settings.fdsCoreRender); - conf.set("c64CoreRender",settings.c64CoreRender); - conf.set("pokeyCoreRender",settings.pokeyCoreRender); - conf.set("opnCoreRender",settings.opnCoreRender); - conf.set("opl2CoreRender",settings.opl2CoreRender); - conf.set("opl3CoreRender",settings.opl3CoreRender); - conf.set("pcSpeakerOutMethod",settings.pcSpeakerOutMethod); - conf.set("yrw801Path",settings.yrw801Path); - conf.set("tg100Path",settings.tg100Path); - conf.set("mu5Path",settings.mu5Path); - conf.set("mainFont",settings.mainFont); - conf.set("headFont",settings.headFont); - conf.set("patFont",settings.patFont); - conf.set("mainFontPath",settings.mainFontPath); - conf.set("headFontPath",settings.headFontPath); - conf.set("patFontPath",settings.patFontPath); + // general + if (groups&GUI_SETTINGS_GENERAL) { + conf.set("renderDriver",settings.renderDriver); + conf.set("noDMFCompat",settings.noDMFCompat); + + conf.set("dpiScale",settings.dpiScale); + + conf.set("initialSys2",settings.initialSys.toBase64()); + conf.set("initialSysName",settings.initialSysName); + } + + // audio + if (groups&GUI_SETTINGS_AUDIO) { + conf.set("audioEngine",String(audioBackends[settings.audioEngine])); + conf.set("audioDevice",settings.audioDevice); + conf.set("midiInDevice",settings.midiInDevice); + conf.set("midiOutDevice",settings.midiOutDevice); + conf.set("sdlAudioDriver",settings.sdlAudioDriver); + conf.set("audioQuality",settings.audioQuality); + conf.set("audioHiPass",settings.audioHiPass); + conf.set("audioBufSize",settings.audioBufSize); + conf.set("audioRate",settings.audioRate); + conf.set("audioChans",settings.audioChans); + } + + // MIDI + if (groups&GUI_SETTINGS_MIDI) { + + } + + // keyboard + if (groups&GUI_SETTINGS_KEYBOARD) { + // keybinds + for (int i=0; i Date: Sat, 23 Dec 2023 04:39:55 -0500 Subject: [PATCH 37/53] just a bit more --- src/gui/settings.cpp | 275 +++++++++++++++++++++++-------------------- 1 file changed, 150 insertions(+), 125 deletions(-) diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index b71582c7f..3867166f4 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -4130,6 +4130,22 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("initialSys2",settings.initialSys.toBase64()); conf.set("initialSysName",settings.initialSysName); + + conf.set("noThreadedInput",settings.noThreadedInput); + conf.set("powerSave",settings.powerSave); + conf.set("eventDelay",settings.eventDelay); + + conf.set("renderBackend",settings.renderBackend); + conf.set("renderClearPos",settings.renderClearPos); + + conf.set("chanOscThreads",settings.chanOscThreads); + conf.set("renderPoolThreads",settings.renderPoolThreads); + conf.set("showPool",settings.showPool); + conf.set("writeInsNames",settings.writeInsNames); + conf.set("readInsNames",settings.readInsNames); + conf.set("defaultAuthorName",settings.defaultAuthorName); + + conf.set("hiddenSystems",settings.hiddenSystems); } // audio @@ -4144,11 +4160,24 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("audioBufSize",settings.audioBufSize); conf.set("audioRate",settings.audioRate); conf.set("audioChans",settings.audioChans); + + conf.set("lowLatency",settings.lowLatency); + + conf.set("metroVol",settings.metroVol); + conf.set("sampleVol",settings.sampleVol); + + conf.set("wasapiEx",settings.wasapiEx); + + conf.set("clampSamples",settings.clampSamples); } // MIDI if (groups&GUI_SETTINGS_MIDI) { - + conf.set("midiOutClock",settings.midiOutClock); + conf.set("midiOutTime",settings.midiOutTime); + conf.set("midiOutProgramChange",settings.midiOutProgramChange); + conf.set("midiOutMode",settings.midiOutMode); + conf.set("midiOutTimeRate",settings.midiOutTimeRate); } // keyboard @@ -4185,6 +4214,12 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("loadChinese",settings.loadChinese); conf.set("loadChineseTraditional",settings.loadChineseTraditional); conf.set("loadKorean",settings.loadKorean); + + conf.set("fontBackend",settings.fontBackend); + conf.set("fontHinting",settings.fontHinting); + conf.set("fontBitmap",settings.fontBitmap); + conf.set("fontAutoHint",settings.fontAutoHint); + conf.set("fontAntiAlias",settings.fontAntiAlias); } // appearance @@ -4203,6 +4238,33 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("channelFeedbackStyle",settings.channelFeedbackStyle); conf.set("channelFont",settings.channelFont); conf.set("channelTextCenter",settings.channelTextCenter); + + conf.set("roundedWindows",settings.roundedWindows); + conf.set("roundedButtons",settings.roundedButtons); + conf.set("roundedMenus",settings.roundedMenus); + + conf.set("separateFMColors",settings.separateFMColors); + conf.set("insEditColorize",settings.insEditColorize); + + conf.set("chipNames",settings.chipNames); + conf.set("overflowHighlight",settings.overflowHighlight); + conf.set("partyTime",settings.partyTime); + conf.set("flatNotes",settings.flatNotes); + conf.set("germanNotation",settings.germanNotation); + + conf.set("frameBorders",settings.frameBorders); + + conf.set("noteOffLabel",settings.noteOffLabel); + conf.set("noteRelLabel",settings.noteRelLabel); + conf.set("macroRelLabel",settings.macroRelLabel); + conf.set("emptyLabel",settings.emptyLabel); + conf.set("emptyLabel2",settings.emptyLabel2); + + conf.set("noteCellSpacing",settings.noteCellSpacing); + conf.set("insCellSpacing",settings.insCellSpacing); + conf.set("volCellSpacing",settings.volCellSpacing); + conf.set("effectCellSpacing",settings.effectCellSpacing); + conf.set("effectValCellSpacing",settings.effectValCellSpacing); } // layout @@ -4255,131 +4317,94 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("mu5Path",settings.mu5Path); } - // TODO: the fucking rest - conf.set("patRowsBase",settings.patRowsBase); - conf.set("orderRowsBase",settings.orderRowsBase); - conf.set("soloAction",settings.soloAction); - conf.set("pullDeleteBehavior",settings.pullDeleteBehavior); - conf.set("wrapHorizontal",settings.wrapHorizontal); - conf.set("wrapVertical",settings.wrapVertical); - conf.set("macroView",settings.macroView); - conf.set("fmNames",settings.fmNames); - conf.set("allowEditDocking",settings.allowEditDocking); - conf.set("chipNames",settings.chipNames); - conf.set("overflowHighlight",settings.overflowHighlight); - conf.set("partyTime",settings.partyTime); - conf.set("flatNotes",settings.flatNotes); - conf.set("germanNotation",settings.germanNotation); - conf.set("stepOnDelete",settings.stepOnDelete); - conf.set("scrollStep",settings.scrollStep); - conf.set("sysSeparators",settings.sysSeparators); - conf.set("forceMono",settings.forceMono); - conf.set("controlLayout",settings.controlLayout); - conf.set("statusDisplay",settings.statusDisplay); - conf.set("viewPrevPattern",settings.viewPrevPattern); - conf.set("avoidRaisingPattern",settings.avoidRaisingPattern); - conf.set("insFocusesPattern",settings.insFocusesPattern); - conf.set("stepOnInsert",settings.stepOnInsert); - conf.set("unifiedDataView",settings.unifiedDataView); - conf.set("sysFileDialog",settings.sysFileDialog); - conf.set("roundedWindows",settings.roundedWindows); - conf.set("roundedButtons",settings.roundedButtons); - conf.set("roundedMenus",settings.roundedMenus); - conf.set("susPosition",settings.susPosition); - conf.set("effectCursorDir",settings.effectCursorDir); - conf.set("cursorPastePos",settings.cursorPastePos); - conf.set("titleBarInfo",settings.titleBarInfo); - conf.set("titleBarSys",settings.titleBarSys); - conf.set("frameBorders",settings.frameBorders); - conf.set("effectDeletionAltersValue",settings.effectDeletionAltersValue); + conf.set("patRowsBase",settings.patRowsBase); + conf.set("orderRowsBase",settings.orderRowsBase); + conf.set("soloAction",settings.soloAction); + conf.set("pullDeleteBehavior",settings.pullDeleteBehavior); + conf.set("wrapHorizontal",settings.wrapHorizontal); + conf.set("wrapVertical",settings.wrapVertical); + conf.set("macroView",settings.macroView); + conf.set("fmNames",settings.fmNames); + conf.set("allowEditDocking",settings.allowEditDocking); + + conf.set("stepOnDelete",settings.stepOnDelete); + conf.set("scrollStep",settings.scrollStep); + conf.set("sysSeparators",settings.sysSeparators); + conf.set("forceMono",settings.forceMono); + conf.set("controlLayout",settings.controlLayout); + conf.set("statusDisplay",settings.statusDisplay); + conf.set("viewPrevPattern",settings.viewPrevPattern); + conf.set("avoidRaisingPattern",settings.avoidRaisingPattern); + conf.set("insFocusesPattern",settings.insFocusesPattern); + conf.set("stepOnInsert",settings.stepOnInsert); + conf.set("unifiedDataView",settings.unifiedDataView); + conf.set("sysFileDialog",settings.sysFileDialog); + conf.set("susPosition",settings.susPosition); + conf.set("effectCursorDir",settings.effectCursorDir); + conf.set("cursorPastePos",settings.cursorPastePos); + conf.set("titleBarInfo",settings.titleBarInfo); + conf.set("titleBarSys",settings.titleBarSys); + + conf.set("effectDeletionAltersValue",settings.effectDeletionAltersValue); - conf.set("separateFMColors",settings.separateFMColors); - conf.set("insEditColorize",settings.insEditColorize); - conf.set("metroVol",settings.metroVol); - conf.set("sampleVol",settings.sampleVol); - conf.set("pushNibble",settings.pushNibble); - conf.set("scrollChangesOrder",settings.scrollChangesOrder); - conf.set("oplStandardWaveNames",settings.oplStandardWaveNames); - conf.set("cursorMoveNoScroll",settings.cursorMoveNoScroll); - conf.set("lowLatency",settings.lowLatency); - conf.set("notePreviewBehavior",settings.notePreviewBehavior); - conf.set("powerSave",settings.powerSave); - conf.set("absorbInsInput",settings.absorbInsInput); - conf.set("eventDelay",settings.eventDelay); - conf.set("moveWindowTitle",settings.moveWindowTitle); - conf.set("hiddenSystems",settings.hiddenSystems); - conf.set("horizontalDataView",settings.horizontalDataView); - conf.set("noMultiSystem",settings.noMultiSystem); - conf.set("oldMacroVSlider",settings.oldMacroVSlider); - conf.set("displayAllInsTypes",settings.displayAllInsTypes); - conf.set("displayPartial",settings.displayPartial); - conf.set("noteCellSpacing",settings.noteCellSpacing); - conf.set("insCellSpacing",settings.insCellSpacing); - conf.set("volCellSpacing",settings.volCellSpacing); - conf.set("effectCellSpacing",settings.effectCellSpacing); - conf.set("effectValCellSpacing",settings.effectValCellSpacing); - conf.set("doubleClickColumn",settings.doubleClickColumn); - conf.set("blankIns",settings.blankIns); - conf.set("dragMovesSelection",settings.dragMovesSelection); - conf.set("unsignedDetune",settings.unsignedDetune); - conf.set("noThreadedInput",settings.noThreadedInput); - conf.set("saveWindowPos",settings.saveWindowPos); - conf.set("clampSamples",settings.clampSamples); - conf.set("noteOffLabel",settings.noteOffLabel); - conf.set("noteRelLabel",settings.noteRelLabel); - conf.set("macroRelLabel",settings.macroRelLabel); - conf.set("emptyLabel",settings.emptyLabel); - conf.set("emptyLabel2",settings.emptyLabel2); - conf.set("saveUnusedPatterns",settings.saveUnusedPatterns); - conf.set("maxRecentFile",settings.maxRecentFile); - conf.set("midiOutClock",settings.midiOutClock); - conf.set("midiOutTime",settings.midiOutTime); - conf.set("midiOutProgramChange",settings.midiOutProgramChange); - conf.set("midiOutMode",settings.midiOutMode); - conf.set("midiOutTimeRate",settings.midiOutTimeRate); - conf.set("centerPattern",settings.centerPattern); - conf.set("ordersCursor",settings.ordersCursor); - conf.set("persistFadeOut",settings.persistFadeOut); - conf.set("exportLoops",settings.exportLoops); - conf.set("exportFadeOut",settings.exportFadeOut); - conf.set("macroLayout",settings.macroLayout); - conf.set("doubleClickTime",settings.doubleClickTime); - conf.set("oneDigitEffects",settings.oneDigitEffects); - conf.set("disableFadeIn",settings.disableFadeIn); - conf.set("alwaysPlayIntro",settings.alwaysPlayIntro); - conf.set("cursorFollowsOrder",settings.cursorFollowsOrder); - conf.set("iCannotWait",settings.iCannotWait); - conf.set("orderButtonPos",settings.orderButtonPos); - conf.set("compress",settings.compress); - conf.set("newPatternFormat",settings.newPatternFormat); - conf.set("renderBackend",settings.renderBackend); - conf.set("renderClearPos",settings.renderClearPos); - conf.set("insertBehavior",settings.insertBehavior); - conf.set("pullDeleteRow",settings.pullDeleteRow); - conf.set("newSongBehavior",settings.newSongBehavior); - conf.set("memUsageUnit",settings.memUsageUnit); - conf.set("cursorFollowsWheel",settings.cursorFollowsWheel); - conf.set("removeInsOff",settings.removeInsOff); - conf.set("removeVolOff",settings.removeVolOff); - conf.set("playOnLoad",settings.playOnLoad); - conf.set("insTypeMenu",settings.insTypeMenu); - conf.set("capitalMenuBar",settings.capitalMenuBar); - conf.set("centerPopup",settings.centerPopup); - conf.set("insIconsStyle",settings.insIconsStyle); - conf.set("classicChipOptions",settings.classicChipOptions); - conf.set("wasapiEx",settings.wasapiEx); - conf.set("chanOscThreads",settings.chanOscThreads); - conf.set("renderPoolThreads",settings.renderPoolThreads); - conf.set("showPool",settings.showPool); - conf.set("writeInsNames",settings.writeInsNames); - conf.set("readInsNames",settings.readInsNames); - conf.set("defaultAuthorName",settings.defaultAuthorName); - conf.set("fontBackend",settings.fontBackend); - conf.set("fontHinting",settings.fontHinting); - conf.set("fontBitmap",settings.fontBitmap); - conf.set("fontAutoHint",settings.fontAutoHint); - conf.set("fontAntiAlias",settings.fontAntiAlias); - conf.set("selectAssetOnLoad",settings.selectAssetOnLoad); + + conf.set("pushNibble",settings.pushNibble); + conf.set("scrollChangesOrder",settings.scrollChangesOrder); + conf.set("oplStandardWaveNames",settings.oplStandardWaveNames); + conf.set("cursorMoveNoScroll",settings.cursorMoveNoScroll); + + conf.set("notePreviewBehavior",settings.notePreviewBehavior); + + conf.set("absorbInsInput",settings.absorbInsInput); + + conf.set("moveWindowTitle",settings.moveWindowTitle); + + conf.set("horizontalDataView",settings.horizontalDataView); + conf.set("noMultiSystem",settings.noMultiSystem); + conf.set("oldMacroVSlider",settings.oldMacroVSlider); + conf.set("displayAllInsTypes",settings.displayAllInsTypes); + conf.set("displayPartial",settings.displayPartial); + conf.set("doubleClickColumn",settings.doubleClickColumn); + conf.set("blankIns",settings.blankIns); + conf.set("dragMovesSelection",settings.dragMovesSelection); + conf.set("unsignedDetune",settings.unsignedDetune); + + conf.set("saveWindowPos",settings.saveWindowPos); + + conf.set("saveUnusedPatterns",settings.saveUnusedPatterns); + conf.set("maxRecentFile",settings.maxRecentFile); + + conf.set("centerPattern",settings.centerPattern); + conf.set("ordersCursor",settings.ordersCursor); + conf.set("persistFadeOut",settings.persistFadeOut); + conf.set("exportLoops",settings.exportLoops); + conf.set("exportFadeOut",settings.exportFadeOut); + conf.set("macroLayout",settings.macroLayout); + conf.set("doubleClickTime",settings.doubleClickTime); + conf.set("oneDigitEffects",settings.oneDigitEffects); + conf.set("disableFadeIn",settings.disableFadeIn); + conf.set("alwaysPlayIntro",settings.alwaysPlayIntro); + conf.set("cursorFollowsOrder",settings.cursorFollowsOrder); + conf.set("iCannotWait",settings.iCannotWait); + conf.set("orderButtonPos",settings.orderButtonPos); + conf.set("compress",settings.compress); + conf.set("newPatternFormat",settings.newPatternFormat); + + conf.set("insertBehavior",settings.insertBehavior); + conf.set("pullDeleteRow",settings.pullDeleteRow); + conf.set("newSongBehavior",settings.newSongBehavior); + conf.set("memUsageUnit",settings.memUsageUnit); + conf.set("cursorFollowsWheel",settings.cursorFollowsWheel); + conf.set("removeInsOff",settings.removeInsOff); + conf.set("removeVolOff",settings.removeVolOff); + conf.set("playOnLoad",settings.playOnLoad); + conf.set("insTypeMenu",settings.insTypeMenu); + conf.set("capitalMenuBar",settings.capitalMenuBar); + conf.set("centerPopup",settings.centerPopup); + conf.set("insIconsStyle",settings.insIconsStyle); + conf.set("classicChipOptions",settings.classicChipOptions); + + conf.set("selectAssetOnLoad",settings.selectAssetOnLoad); } void FurnaceGUI::syncSettings() { From e42bccdb50861123686eb5a03fd2ccb75cdc5525 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 23 Dec 2023 17:51:08 -0500 Subject: [PATCH 38/53] GUI: writeConfig now grouped --- src/gui/settings.cpp | 184 ++++++++++++++++++++++--------------------- 1 file changed, 93 insertions(+), 91 deletions(-) diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 3867166f4..9cc08fd82 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -3754,7 +3754,6 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { settings.pullDeleteBehavior=conf.getInt("pullDeleteBehavior",1); settings.wrapHorizontal=conf.getInt("wrapHorizontal",0); settings.wrapVertical=conf.getInt("wrapVertical",0); - settings.macroView=conf.getInt("macroView",0); settings.fmNames=conf.getInt("fmNames",0); settings.allowEditDocking=conf.getInt("allowEditDocking",1); settings.chipNames=conf.getInt("chipNames",0); @@ -3938,7 +3937,6 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { clampSetting(settings.pullDeleteBehavior,0,1); clampSetting(settings.wrapHorizontal,0,2); clampSetting(settings.wrapVertical,0,3); - clampSetting(settings.macroView,0,1); clampSetting(settings.fmNames,0,2); clampSetting(settings.allowEditDocking,0,1); clampSetting(settings.chipNames,0,1); @@ -4146,6 +4144,32 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("defaultAuthorName",settings.defaultAuthorName); conf.set("hiddenSystems",settings.hiddenSystems); + conf.set("allowEditDocking",settings.allowEditDocking); + conf.set("sysFileDialog",settings.sysFileDialog); + conf.set("displayAllInsTypes",settings.displayAllInsTypes); + conf.set("displayPartial",settings.displayPartial); + + conf.set("blankIns",settings.blankIns); + + conf.set("saveWindowPos",settings.saveWindowPos); + + conf.set("saveUnusedPatterns",settings.saveUnusedPatterns); + conf.set("maxRecentFile",settings.maxRecentFile); + + conf.set("persistFadeOut",settings.persistFadeOut); + conf.set("exportLoops",settings.exportLoops); + conf.set("exportFadeOut",settings.exportFadeOut); + + conf.set("doubleClickTime",settings.doubleClickTime); + conf.set("disableFadeIn",settings.disableFadeIn); + conf.set("alwaysPlayIntro",settings.alwaysPlayIntro); + conf.set("iCannotWait",settings.iCannotWait); + + conf.set("compress",settings.compress); + conf.set("newPatternFormat",settings.newPatternFormat); + conf.set("newSongBehavior",settings.newSongBehavior); + conf.set("playOnLoad",settings.playOnLoad); + conf.set("centerPopup",settings.centerPopup); } // audio @@ -4169,6 +4193,7 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("wasapiEx",settings.wasapiEx); conf.set("clampSamples",settings.clampSamples); + conf.set("forceMono",settings.forceMono); } // MIDI @@ -4193,7 +4218,44 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { // behavior if (groups&GUI_SETTINGS_BEHAVIOR) { + conf.set("soloAction",settings.soloAction); + conf.set("pullDeleteBehavior",settings.pullDeleteBehavior); + conf.set("wrapHorizontal",settings.wrapHorizontal); + conf.set("wrapVertical",settings.wrapVertical); + + conf.set("stepOnDelete",settings.stepOnDelete); + conf.set("scrollStep",settings.scrollStep); + conf.set("avoidRaisingPattern",settings.avoidRaisingPattern); + conf.set("insFocusesPattern",settings.insFocusesPattern); + conf.set("stepOnInsert",settings.stepOnInsert); + conf.set("effectCursorDir",settings.effectCursorDir); + conf.set("cursorPastePos",settings.cursorPastePos); + + conf.set("effectDeletionAltersValue",settings.effectDeletionAltersValue); + conf.set("pushNibble",settings.pushNibble); + conf.set("scrollChangesOrder",settings.scrollChangesOrder); + conf.set("cursorMoveNoScroll",settings.cursorMoveNoScroll); + + conf.set("notePreviewBehavior",settings.notePreviewBehavior); + + conf.set("absorbInsInput",settings.absorbInsInput); + + conf.set("moveWindowTitle",settings.moveWindowTitle); + + conf.set("doubleClickColumn",settings.doubleClickColumn); + conf.set("dragMovesSelection",settings.dragMovesSelection); + + conf.set("cursorFollowsOrder",settings.cursorFollowsOrder); + + conf.set("insertBehavior",settings.insertBehavior); + conf.set("pullDeleteRow",settings.pullDeleteRow); + conf.set("cursorFollowsWheel",settings.cursorFollowsWheel); + conf.set("removeInsOff",settings.removeInsOff); + conf.set("removeVolOff",settings.removeVolOff); + conf.set("insTypeMenu",settings.insTypeMenu); + + conf.set("selectAssetOnLoad",settings.selectAssetOnLoad); } // font @@ -4265,6 +4327,31 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("volCellSpacing",settings.volCellSpacing); conf.set("effectCellSpacing",settings.effectCellSpacing); conf.set("effectValCellSpacing",settings.effectValCellSpacing); + + conf.set("patRowsBase",settings.patRowsBase); + conf.set("orderRowsBase",settings.orderRowsBase); + conf.set("fmNames",settings.fmNames); + conf.set("statusDisplay",settings.statusDisplay); + conf.set("viewPrevPattern",settings.viewPrevPattern); + conf.set("susPosition",settings.susPosition); + + conf.set("titleBarInfo",settings.titleBarInfo); + conf.set("titleBarSys",settings.titleBarSys); + + conf.set("oplStandardWaveNames",settings.oplStandardWaveNames); + + conf.set("horizontalDataView",settings.horizontalDataView); + conf.set("noMultiSystem",settings.noMultiSystem); + conf.set("oldMacroVSlider",settings.oldMacroVSlider); + conf.set("unsignedDetune",settings.unsignedDetune); + conf.set("centerPattern",settings.centerPattern); + conf.set("ordersCursor",settings.ordersCursor); + conf.set("oneDigitEffects",settings.oneDigitEffects); + conf.set("orderButtonPos",settings.orderButtonPos); + conf.set("memUsageUnit",settings.memUsageUnit); + conf.set("capitalMenuBar",settings.capitalMenuBar); + conf.set("insIconsStyle",settings.insIconsStyle); + conf.set("sysSeparators",settings.sysSeparators); } // layout @@ -4273,6 +4360,10 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("sampleLayout",settings.sampleLayout); conf.set("waveLayout",settings.waveLayout); conf.set("exportOptionsLayout",settings.exportOptionsLayout); + conf.set("unifiedDataView",settings.unifiedDataView); + conf.set("macroLayout",settings.macroLayout); + conf.set("controlLayout",settings.controlLayout); + conf.set("classicChipOptions",settings.classicChipOptions); } // color @@ -4316,95 +4407,6 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("tg100Path",settings.tg100Path); conf.set("mu5Path",settings.mu5Path); } - - conf.set("patRowsBase",settings.patRowsBase); - conf.set("orderRowsBase",settings.orderRowsBase); - conf.set("soloAction",settings.soloAction); - conf.set("pullDeleteBehavior",settings.pullDeleteBehavior); - conf.set("wrapHorizontal",settings.wrapHorizontal); - conf.set("wrapVertical",settings.wrapVertical); - conf.set("macroView",settings.macroView); - conf.set("fmNames",settings.fmNames); - conf.set("allowEditDocking",settings.allowEditDocking); - - conf.set("stepOnDelete",settings.stepOnDelete); - conf.set("scrollStep",settings.scrollStep); - conf.set("sysSeparators",settings.sysSeparators); - conf.set("forceMono",settings.forceMono); - conf.set("controlLayout",settings.controlLayout); - conf.set("statusDisplay",settings.statusDisplay); - conf.set("viewPrevPattern",settings.viewPrevPattern); - conf.set("avoidRaisingPattern",settings.avoidRaisingPattern); - conf.set("insFocusesPattern",settings.insFocusesPattern); - conf.set("stepOnInsert",settings.stepOnInsert); - conf.set("unifiedDataView",settings.unifiedDataView); - conf.set("sysFileDialog",settings.sysFileDialog); - conf.set("susPosition",settings.susPosition); - conf.set("effectCursorDir",settings.effectCursorDir); - conf.set("cursorPastePos",settings.cursorPastePos); - conf.set("titleBarInfo",settings.titleBarInfo); - conf.set("titleBarSys",settings.titleBarSys); - - conf.set("effectDeletionAltersValue",settings.effectDeletionAltersValue); - - - conf.set("pushNibble",settings.pushNibble); - conf.set("scrollChangesOrder",settings.scrollChangesOrder); - conf.set("oplStandardWaveNames",settings.oplStandardWaveNames); - conf.set("cursorMoveNoScroll",settings.cursorMoveNoScroll); - - conf.set("notePreviewBehavior",settings.notePreviewBehavior); - - conf.set("absorbInsInput",settings.absorbInsInput); - - conf.set("moveWindowTitle",settings.moveWindowTitle); - - conf.set("horizontalDataView",settings.horizontalDataView); - conf.set("noMultiSystem",settings.noMultiSystem); - conf.set("oldMacroVSlider",settings.oldMacroVSlider); - conf.set("displayAllInsTypes",settings.displayAllInsTypes); - conf.set("displayPartial",settings.displayPartial); - conf.set("doubleClickColumn",settings.doubleClickColumn); - conf.set("blankIns",settings.blankIns); - conf.set("dragMovesSelection",settings.dragMovesSelection); - conf.set("unsignedDetune",settings.unsignedDetune); - - conf.set("saveWindowPos",settings.saveWindowPos); - - conf.set("saveUnusedPatterns",settings.saveUnusedPatterns); - conf.set("maxRecentFile",settings.maxRecentFile); - - conf.set("centerPattern",settings.centerPattern); - conf.set("ordersCursor",settings.ordersCursor); - conf.set("persistFadeOut",settings.persistFadeOut); - conf.set("exportLoops",settings.exportLoops); - conf.set("exportFadeOut",settings.exportFadeOut); - conf.set("macroLayout",settings.macroLayout); - conf.set("doubleClickTime",settings.doubleClickTime); - conf.set("oneDigitEffects",settings.oneDigitEffects); - conf.set("disableFadeIn",settings.disableFadeIn); - conf.set("alwaysPlayIntro",settings.alwaysPlayIntro); - conf.set("cursorFollowsOrder",settings.cursorFollowsOrder); - conf.set("iCannotWait",settings.iCannotWait); - conf.set("orderButtonPos",settings.orderButtonPos); - conf.set("compress",settings.compress); - conf.set("newPatternFormat",settings.newPatternFormat); - - conf.set("insertBehavior",settings.insertBehavior); - conf.set("pullDeleteRow",settings.pullDeleteRow); - conf.set("newSongBehavior",settings.newSongBehavior); - conf.set("memUsageUnit",settings.memUsageUnit); - conf.set("cursorFollowsWheel",settings.cursorFollowsWheel); - conf.set("removeInsOff",settings.removeInsOff); - conf.set("removeVolOff",settings.removeVolOff); - conf.set("playOnLoad",settings.playOnLoad); - conf.set("insTypeMenu",settings.insTypeMenu); - conf.set("capitalMenuBar",settings.capitalMenuBar); - conf.set("centerPopup",settings.centerPopup); - conf.set("insIconsStyle",settings.insIconsStyle); - conf.set("classicChipOptions",settings.classicChipOptions); - - conf.set("selectAssetOnLoad",settings.selectAssetOnLoad); } void FurnaceGUI::syncSettings() { From ed37b5123f831bac5489b3518b3d222d719264b8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 23 Dec 2023 18:10:19 -0500 Subject: [PATCH 39/53] GUI: use refactored code for color scheme export --- src/gui/settings.cpp | 47 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 9cc08fd82..94d312f34 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -3696,6 +3696,36 @@ void FurnaceGUI::drawSettings() { } void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { + if (groups&GUI_SETTINGS_GENERAL) { + } + + if (groups&GUI_SETTINGS_AUDIO) { + } + + if (groups&GUI_SETTINGS_MIDI) { + } + + if (groups&GUI_SETTINGS_KEYBOARD) { + } + + if (groups&GUI_SETTINGS_BEHAVIOR) { + } + + if (groups&GUI_SETTINGS_FONT) { + } + + if (groups&GUI_SETTINGS_APPEARANCE) { + } + + if (groups&GUI_SETTINGS_LAYOUTS) { + } + + if (groups&GUI_SETTINGS_COLOR) { + } + + if (groups&GUI_SETTINGS_EMULATION) { + } + settings.mainFontSize=conf.getInt("mainFontSize",18); settings.headFontSize=conf.getInt("headFontSize",27); settings.patFontSize=conf.getInt("patFontSize",18); @@ -4553,18 +4583,23 @@ bool FurnaceGUI::importColors(String path) { } bool FurnaceGUI::exportColors(String path) { + DivConfig c; + + c.set("configVersion",DIV_ENGINE_VERSION); + writeConfig(c,GUI_SETTINGS_COLOR); + FILE* f=ps_fopen(path.c_str(),"wb"); if (f==NULL) { logW("error while opening color file for export: %s",strerror(errno)); return false; } - fprintf(f,"configVersion=%d\n",DIV_ENGINE_VERSION); - for (int i=0; i Date: Sat, 23 Dec 2023 18:25:43 -0500 Subject: [PATCH 40/53] prepare to fix paate overflow undo corruption --- src/gui/gui.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/gui/gui.h b/src/gui/gui.h index 1cfbfc011..39fac0132 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -885,6 +885,14 @@ struct SelectionPoint { xCoarse(0), xFine(0), y(0) {} }; +struct UndoRegion { + struct UndoRegionPoint { + int ord, x, y; + UndoRegionPoint(): + ord(0), x(0), y(0) {} + } begin, end; +}; + enum ActionType { GUI_UNDO_CHANGE_ORDER, GUI_UNDO_PATTERN_EDIT, From ce8eb34fbf05e50bffdb440fd5ac8a7a042672d4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 23 Dec 2023 18:35:23 -0500 Subject: [PATCH 42/53] update to-do list --- TODO.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/TODO.md b/TODO.md index 06951982a..f1918edef 100644 --- a/TODO.md +++ b/TODO.md @@ -1,15 +1,12 @@ # to-do -- add cheat code to insert bruno time (blank) - -# THE REAL TO-DO LIST - +- finish color import improvements (settings refactor) +- new undo stuff +- fix some bugs - finish auto-clone once you have done all of this (maybe not the first one), release 0.6.1 -Furnace is like alcohol... - # and then - new oscilloscope renderer - custom code that uses texture and fixes two issues: too many vertices, and broken anti-aliasing From e58307684c017530fa72ab6a4cb434d41d8a2911 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 23 Dec 2023 20:05:07 -0500 Subject: [PATCH 43/53] GUI: aaaaaaaand here we go importColors and exportColors now use the same framework as settings --- src/gui/settings.cpp | 592 ++++++++++++++++++++++--------------------- 1 file changed, 298 insertions(+), 294 deletions(-) diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 94d312f34..708fe67b2 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -3697,236 +3697,325 @@ void FurnaceGUI::drawSettings() { void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { if (groups&GUI_SETTINGS_GENERAL) { + settings.renderDriver=conf.getString("renderDriver",""); + settings.noDMFCompat=conf.getInt("noDMFCompat",0); + + settings.dpiScale=conf.getFloat("dpiScale",0.0f); + + settings.initialSysName=conf.getString("initialSysName",""); + + // initial system + String initialSys2=conf.getString("initialSys2",""); + bool oldVol=conf.getInt("configVersion",DIV_ENGINE_VERSION)<135; + if (initialSys2.empty()) { + initialSys2=e->decodeSysDesc(conf.getString("initialSys","")); + oldVol=false; + } + settings.initialSys.clear(); + settings.initialSys.loadFromBase64(initialSys2.c_str()); + if (settings.initialSys.getInt("id0",0)==0) { + settings.initialSys.clear(); + settings.initialSys.set("id0",e->systemToFileFur(DIV_SYSTEM_YM2612)); + settings.initialSys.set("vol0",1.0f); + settings.initialSys.set("pan0",0.0f); + settings.initialSys.set("fr0",0.0f); + settings.initialSys.set("flags0",""); + settings.initialSys.set("id1",e->systemToFileFur(DIV_SYSTEM_SMS)); + settings.initialSys.set("vol1",0.5f); + settings.initialSys.set("pan1",0); + settings.initialSys.set("fr1",0); + settings.initialSys.set("flags1",""); + } else { + if (oldVol) { + for (int i=0; settings.initialSys.getInt(fmt::sprintf("id%d",i),0); i++) { + float newVol=settings.initialSys.getInt(fmt::sprintf("vol%d",i),64); + float newPan=settings.initialSys.getInt(fmt::sprintf("pan%d",i),0); + newVol/=64.0f; + newPan/=127.0f; + settings.initialSys.set(fmt::sprintf("vol%d",i),newVol); + settings.initialSys.set(fmt::sprintf("pan%d",i),newPan); + } + conf.set("initialSys2",settings.initialSys.toBase64()); + conf.set("configVersion",DIV_ENGINE_VERSION); + } + } + + settings.noThreadedInput=conf.getInt("noThreadedInput",0); + settings.powerSave=conf.getInt("powerSave",POWER_SAVE_DEFAULT); + settings.eventDelay=conf.getInt("eventDelay",0); + + settings.renderBackend=conf.getString("renderBackend",GUI_BACKEND_DEFAULT_NAME); + settings.renderClearPos=conf.getInt("renderClearPos",0); + + settings.chanOscThreads=conf.getInt("chanOscThreads",0); + settings.renderPoolThreads=conf.getInt("renderPoolThreads",0); + settings.showPool=conf.getInt("showPool",0); + settings.writeInsNames=conf.getInt("writeInsNames",1); + settings.readInsNames=conf.getInt("readInsNames",1); + settings.defaultAuthorName=conf.getString("defaultAuthorName",""); + + settings.hiddenSystems=conf.getInt("hiddenSystems",0); + settings.allowEditDocking=conf.getInt("allowEditDocking",1); + settings.sysFileDialog=conf.getInt("sysFileDialog",SYS_FILE_DIALOG_DEFAULT); + settings.displayAllInsTypes=conf.getInt("displayAllInsTypes",0); + settings.displayPartial=conf.getInt("displayPartial",0); + + settings.blankIns=conf.getInt("blankIns",0); + + settings.saveWindowPos=conf.getInt("saveWindowPos",1); + + settings.saveUnusedPatterns=conf.getInt("saveUnusedPatterns",0); + settings.maxRecentFile=conf.getInt("maxRecentFile",10); + + settings.persistFadeOut=conf.getInt("persistFadeOut",1); + settings.exportLoops=conf.getInt("exportLoops",0); + settings.exportFadeOut=conf.getDouble("exportFadeOut",0.0); + + settings.doubleClickTime=conf.getFloat("doubleClickTime",0.3f); + settings.disableFadeIn=conf.getInt("disableFadeIn",0); + settings.alwaysPlayIntro=conf.getInt("alwaysPlayIntro",0); + settings.iCannotWait=conf.getInt("iCannotWait",0); + + settings.compress=conf.getInt("compress",1); + settings.newPatternFormat=conf.getInt("newPatternFormat",1); + settings.newSongBehavior=conf.getInt("newSongBehavior",0); + settings.playOnLoad=conf.getInt("playOnLoad",0); + settings.centerPopup=conf.getInt("centerPopup",1); } if (groups&GUI_SETTINGS_AUDIO) { + settings.audioEngine=(conf.getString("audioEngine","SDL")=="SDL")?1:0; + if (conf.getString("audioEngine","SDL")=="JACK") { + settings.audioEngine=DIV_AUDIO_JACK; + } else if (conf.getString("audioEngine","SDL")=="PortAudio") { + settings.audioEngine=DIV_AUDIO_PORTAUDIO; + } else { + settings.audioEngine=DIV_AUDIO_SDL; + } + settings.audioDevice=conf.getString("audioDevice",""); + settings.sdlAudioDriver=conf.getString("sdlAudioDriver",""); + settings.audioQuality=conf.getInt("audioQuality",0); + settings.audioHiPass=conf.getInt("audioHiPass",1); + settings.audioBufSize=conf.getInt("audioBufSize",1024); + settings.audioRate=conf.getInt("audioRate",44100); + settings.audioChans=conf.getInt("audioChans",2); + + settings.lowLatency=conf.getInt("lowLatency",0); + + settings.metroVol=conf.getInt("metroVol",100); + settings.sampleVol=conf.getInt("sampleVol",50); + + settings.wasapiEx=conf.getInt("wasapiEx",0); + + settings.clampSamples=conf.getInt("clampSamples",0); + settings.forceMono=conf.getInt("forceMono",0); } if (groups&GUI_SETTINGS_MIDI) { + settings.midiInDevice=conf.getString("midiInDevice",""); + settings.midiOutDevice=conf.getString("midiOutDevice",""); + settings.midiOutClock=conf.getInt("midiOutClock",0); + settings.midiOutTime=conf.getInt("midiOutTime",0); + settings.midiOutProgramChange=conf.getInt("midiOutProgramChange",0); + settings.midiOutMode=conf.getInt("midiOutMode",1); + settings.midiOutTimeRate=conf.getInt("midiOutTimeRate",0); } if (groups&GUI_SETTINGS_KEYBOARD) { + // keybinds + for (int i=0; idecodeSysDesc(conf.getString("initialSys","")); - oldVol=false; - } - settings.initialSys.clear(); - settings.initialSys.loadFromBase64(initialSys2.c_str()); - if (settings.initialSys.getInt("id0",0)==0) { - settings.initialSys.clear(); - settings.initialSys.set("id0",e->systemToFileFur(DIV_SYSTEM_YM2612)); - settings.initialSys.set("vol0",1.0f); - settings.initialSys.set("pan0",0.0f); - settings.initialSys.set("fr0",0.0f); - settings.initialSys.set("flags0",""); - settings.initialSys.set("id1",e->systemToFileFur(DIV_SYSTEM_SMS)); - settings.initialSys.set("vol1",0.5f); - settings.initialSys.set("pan1",0); - settings.initialSys.set("fr1",0); - settings.initialSys.set("flags1",""); - } else { - if (oldVol) { - for (int i=0; settings.initialSys.getInt(fmt::sprintf("id%d",i),0); i++) { - float newVol=settings.initialSys.getInt(fmt::sprintf("vol%d",i),64); - float newPan=settings.initialSys.getInt(fmt::sprintf("pan%d",i),0); - newVol/=64.0f; - newPan/=127.0f; - settings.initialSys.set(fmt::sprintf("vol%d",i),newVol); - settings.initialSys.set(fmt::sprintf("pan%d",i),newPan); - } - conf.set("initialSys2",settings.initialSys.toBase64()); - conf.set("configVersion",DIV_ENGINE_VERSION); - } - } + if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0; } void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { @@ -4206,8 +4246,6 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { if (groups&GUI_SETTINGS_AUDIO) { conf.set("audioEngine",String(audioBackends[settings.audioEngine])); conf.set("audioDevice",settings.audioDevice); - conf.set("midiInDevice",settings.midiInDevice); - conf.set("midiOutDevice",settings.midiOutDevice); conf.set("sdlAudioDriver",settings.sdlAudioDriver); conf.set("audioQuality",settings.audioQuality); conf.set("audioHiPass",settings.audioHiPass); @@ -4228,6 +4266,8 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { // MIDI if (groups&GUI_SETTINGS_MIDI) { + conf.set("midiInDevice",settings.midiInDevice); + conf.set("midiOutDevice",settings.midiOutDevice); conf.set("midiOutClock",settings.midiOutClock); conf.set("midiOutTime",settings.midiOutTime); conf.set("midiOutProgramChange",settings.midiOutProgramChange); @@ -4534,50 +4574,14 @@ void FurnaceGUI::commitSettings() { } bool FurnaceGUI::importColors(String path) { - FILE* f=ps_fopen(path.c_str(),"rb"); - if (f==NULL) { + DivConfig c; + if (!c.loadFromFile(path.c_str(),false,false)) { logW("error while opening color file for import: %s",strerror(errno)); return false; } - resetColors(); - char line[4096]; - while (!feof(f)) { - String key=""; - String value=""; - bool keyOrValue=false; - if (fgets(line,4095,f)==NULL) { - break; - } - for (char* i=line; *i; i++) { - if (*i=='\n') continue; - if (keyOrValue) { - value+=*i; - } else { - if (*i=='=') { - keyOrValue=true; - } else { - key+=*i; - } - } - } - if (keyOrValue) { - // unoptimal - const char* cs=key.c_str(); - for (int i=0; i Date: Sun, 24 Dec 2023 16:59:09 -0500 Subject: [PATCH 44/53] set writeInsNames to 0 by default so we can beat .dmp --- src/gui/gui.h | 2 +- src/gui/settings.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index 39fac0132..51abb74ae 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1911,7 +1911,7 @@ class FurnaceGUI { chanOscThreads(0), renderPoolThreads(0), showPool(0), - writeInsNames(1), + writeInsNames(0), readInsNames(1), fontBackend(1), fontHinting(0), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 708fe67b2..8c503d72b 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -3750,7 +3750,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { settings.chanOscThreads=conf.getInt("chanOscThreads",0); settings.renderPoolThreads=conf.getInt("renderPoolThreads",0); settings.showPool=conf.getInt("showPool",0); - settings.writeInsNames=conf.getInt("writeInsNames",1); + settings.writeInsNames=conf.getInt("writeInsNames",0); settings.readInsNames=conf.getInt("readInsNames",1); settings.defaultAuthorName=conf.getString("defaultAuthorName",""); From 8eed9ff94079ad995fd7c5cf3efea9fbbe8a1950 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 26 Dec 2023 14:56:24 -0500 Subject: [PATCH 45/53] aaaaa --- src/gui/editing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 22e0249f5..50c92aadf 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -1117,7 +1117,7 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String if (invalidData) { - logW("invalid OpenMPT clipboard data! failed at line %d char %d",i,charPos); + logW("invalid clipboard data! failed at line %d char %d",i,charPos); logW("%s",line.c_str()); break; } From 07b037349b2419736359f25e114e5798e17bb327 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 27 Dec 2023 14:49:35 -0500 Subject: [PATCH 46/53] fix text export crash --- src/engine/fileOps.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index cb6f4fa90..1dd9d89d4 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -6492,7 +6492,7 @@ SafeWriter* DivEngine::saveText(bool separatePatterns) { if (ins->type==DIV_INS_GB) { w->writeText("- Game Boy parameters:\n"); w->writeText(fmt::sprintf(" - volume: %d\n",ins->gb.envVol)); - w->writeText(fmt::sprintf(" - direction: %d\n",gbEnvDir[ins->gb.envDir?1:0])); + w->writeText(fmt::sprintf(" - direction: %s\n",gbEnvDir[ins->gb.envDir?1:0])); w->writeText(fmt::sprintf(" - length: %d\n",ins->gb.envLen)); w->writeText(fmt::sprintf(" - sound length: %d\n",ins->gb.soundLen)); w->writeText(fmt::sprintf(" - use software envelope: %s\n",trueFalse[ins->gb.softEnv?1:0])); From fd4570f973aeeb63fb029b772e4e170f05baa058 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 28 Dec 2023 14:37:10 -0500 Subject: [PATCH 47/53] fix description of 88xy effect --- src/engine/engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index b2150ec1b..b425f5dc1 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -83,7 +83,7 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul case 0x82: return "82xx: Set panning (right channel)"; case 0x88: - return "88xx: Set panning (rear channels; x: left; y: right)"; + return "88xy: Set panning (rear channels; x: left; y: right)"; break; case 0x89: return "89xx: Set panning (rear left channel)"; From 65b50f4accb827e512883f3e436b3f1810c525b2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 28 Dec 2023 17:32:40 -0500 Subject: [PATCH 48/53] GUI: improve mobile order input --- src/gui/gui.cpp | 51 +++++++++++++++++++++++++--------------------- src/gui/gui.h | 1 + src/gui/orders.cpp | 8 ++++++-- src/gui/piano.cpp | 12 +++++++---- 4 files changed, 43 insertions(+), 29 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 7d17af0ee..b8da63237 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1319,6 +1319,32 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) { } } +void FurnaceGUI::orderInput(int num) { + if (orderCursor>=0 && orderCursorgetTotalChannelCount()) { + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->lockSave([this,num]() { + if (!curNibble && !settings.pushNibble) e->curOrders->ord[orderCursor][curOrder]=0; + e->curOrders->ord[orderCursor][curOrder]=((e->curOrders->ord[orderCursor][curOrder]<<4)|num); + }); + MARK_MODIFIED; + curNibble=!curNibble; + if (orderEditMode==2 || orderEditMode==3) { + if (!curNibble) { + if (orderEditMode==2) { + orderCursor++; + if (orderCursor>=e->getTotalChannelCount()) orderCursor=0; + } else if (orderEditMode==3) { + if (curOrdercurSubSong->ordersLen-1) { + setOrder(curOrder+1); + } + } + } + } + e->walkSong(loopOrder,loopRow,loopEnd); + makeUndo(GUI_UNDO_CHANGE_ORDER); + } +} + #define changeLatch(x) \ if (x<0) x=0; \ if (!latchNibble && !settings.pushNibble) x=0; \ @@ -1529,29 +1555,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { auto it=valueKeys.find(ev.key.keysym.sym); if (it!=valueKeys.cend()) { int num=it->second; - if (orderCursor>=0 && orderCursorgetTotalChannelCount()) { - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->lockSave([this,num]() { - if (!curNibble && !settings.pushNibble) e->curOrders->ord[orderCursor][curOrder]=0; - e->curOrders->ord[orderCursor][curOrder]=((e->curOrders->ord[orderCursor][curOrder]<<4)|num); - }); - MARK_MODIFIED; - curNibble=!curNibble; - if (orderEditMode==2 || orderEditMode==3) { - if (!curNibble) { - if (orderEditMode==2) { - orderCursor++; - if (orderCursor>=e->getTotalChannelCount()) orderCursor=0; - } else if (orderEditMode==3) { - if (curOrdercurSubSong->ordersLen-1) { - setOrder(curOrder+1); - } - } - } - } - e->walkSong(loopOrder,loopRow,loopEnd); - makeUndo(GUI_UNDO_CHANGE_ORDER); - } + orderInput(num); } } break; @@ -4518,6 +4522,7 @@ bool FurnaceGUI::loop() { ordersOpen=true; curWindow=GUI_WINDOW_ORDERS; MEASURE(orders,drawOrders()); + MEASURE(piano,drawPiano()); break; case GUI_SCENE_INSTRUMENT: insEditOpen=true; diff --git a/src/gui/gui.h b/src/gui/gui.h index 51abb74ae..0e78b3b6e 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2555,6 +2555,7 @@ class FurnaceGUI { DivSystem systemPicker(); void noteInput(int num, int key, int vol=-1); void valueInput(int num, bool direct=false, int target=-1); + void orderInput(int num); void doGenerateWave(); diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 43dba3d79..4b0ae27d0 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -181,6 +181,10 @@ void FurnaceGUI::drawOrderButtons() { } NEXT_BUTTON; + if (orderEditMode==0 && mobileUI) { + orderEditMode=1; + } + const char* orderEditModeLabel="?##OrderEditMode"; if (orderEditMode==3) { orderEditModeLabel=ICON_FA_ARROWS_V "##OrderEditMode"; @@ -193,7 +197,7 @@ void FurnaceGUI::drawOrderButtons() { } if (ImGui::Button(orderEditModeLabel)) { handleUnimportant orderEditMode++; - if (orderEditMode>3) orderEditMode=0; + if (orderEditMode>3) orderEditMode=mobileUI?1:0; curNibble=false; } if (ImGui::IsItemHovered()) { @@ -219,7 +223,7 @@ void FurnaceGUI::drawOrders() { 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)); + patWindowSize=(portrait?ImVec2(canvasW,canvasH-(0.16*canvasW)-(pianoOpen?(0.4*canvasW):0.0f)):ImVec2(canvasW-(0.16*canvasH),canvasH-(pianoOpen?(0.3*canvasH):0.0f))); ImGui::SetNextWindowPos(patWindowPos); ImGui::SetNextWindowSize(patWindowSize); } else { diff --git a/src/gui/piano.cpp b/src/gui/piano.cpp index a167b1b89..4e20142c7 100644 --- a/src/gui/piano.cpp +++ b/src/gui/piano.cpp @@ -42,7 +42,11 @@ const bool isTopKey[12]={ #define VALUE_DIGIT(x,label) \ if (ImGui::Button(label,buttonSize)) { \ - valueInput(x,false); \ + if (curWindow==GUI_WINDOW_ORDERS && orderEditMode>0) { \ + orderInput(x); \ + } else { \ + valueInput(x,false); \ + } \ } void FurnaceGUI::drawPiano() { @@ -166,7 +170,7 @@ void FurnaceGUI::drawPiano() { } ImGui::TableNextColumn(); - if (pianoInputPadMode==PIANO_INPUT_PAD_REPLACE && cursor.xFine>0 && curWindow==GUI_WINDOW_PATTERN) { + if (pianoInputPadMode==PIANO_INPUT_PAD_REPLACE && ((cursor.xFine>0 && curWindow==GUI_WINDOW_PATTERN) || (curWindow==GUI_WINDOW_ORDERS && orderEditMode>0))) { ImVec2 buttonSize=ImGui::GetContentRegionAvail(); if (ImGui::BeginTable("InputPadP",8,ImGuiTableFlags_SizingFixedSame)) { ImGui::TableNextRow(); @@ -443,9 +447,9 @@ void FurnaceGUI::drawPiano() { ImGui::End(); // draw input pad if necessary - if ((curWindow==GUI_WINDOW_PATTERN || !mobileUI) && ((pianoInputPadMode==PIANO_INPUT_PAD_SPLIT_AUTO && cursor.xFine>0) || pianoInputPadMode==PIANO_INPUT_PAD_SPLIT_VISIBLE)) { + if ((curWindow==GUI_WINDOW_ORDERS || curWindow==GUI_WINDOW_PATTERN || !mobileUI) && ((pianoInputPadMode==PIANO_INPUT_PAD_SPLIT_AUTO && (cursor.xFine>0 || (curWindow==GUI_WINDOW_ORDERS && orderEditMode>0))) || pianoInputPadMode==PIANO_INPUT_PAD_SPLIT_VISIBLE)) { if (ImGui::Begin("Input Pad",NULL,ImGuiWindowFlags_NoTitleBar)) { - ImGui::BeginDisabled(cursor.xFine==0); + ImGui::BeginDisabled(cursor.xFine==0 && !(curWindow==GUI_WINDOW_ORDERS && orderEditMode>0)); if (ImGui::BeginTable("InputPad",3,ImGuiTableFlags_Borders)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); From c1c2b52ba7656c9d1eae6c1c127bc8ae3496655f Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Wed, 27 Dec 2023 08:53:09 +0100 Subject: [PATCH 49/53] Add button in midi settings to refresh midi devices Deletes and creates a new RT midi instance allowing for midi devices to be plugged in without restarting --- src/audio/midi.cpp | 5 +++++ src/audio/taAudio.h | 2 ++ src/engine/engine.cpp | 9 +++++++++ src/engine/engine.h | 3 +++ src/gui/settings.cpp | 7 +++++++ 5 files changed, 26 insertions(+) diff --git a/src/audio/midi.cpp b/src/audio/midi.cpp index fa3faf1f4..ea46cc20e 100644 --- a/src/audio/midi.cpp +++ b/src/audio/midi.cpp @@ -58,4 +58,9 @@ void TAAudio::quitMidi() { delete midiOut; midiOut=NULL; } +} + +bool TAAudio::reinitMidi(bool jack) { + quitMidi(); + initMidi(jack); } \ No newline at end of file diff --git a/src/audio/taAudio.h b/src/audio/taAudio.h index 0b6f530c6..3d21cbaff 100644 --- a/src/audio/taAudio.h +++ b/src/audio/taAudio.h @@ -178,6 +178,8 @@ class TAAudio { virtual std::vector listAudioDevices(); bool initMidi(bool jack); void quitMidi(); + /** remove and reload midi to allow hotswaping midi devices */ + bool reinitMidi(bool jack); virtual bool init(TAAudioDesc& request, TAAudioDesc& response); TAAudio(): diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index b425f5dc1..76c56cb16 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3473,6 +3473,15 @@ void DivEngine::rescanAudioDevices() { } } + +void DivEngine::rescanMidiDevices() { + if (output!=NULL) { + logV("reloading midi..."); + output->reinitMidi(false); + rescanAudioDevices(); + } +} + void DivEngine::initDispatch(bool isRender) { BUSY_BEGIN; logV("initializing dispatch..."); diff --git a/src/engine/engine.h b/src/engine/engine.h index eb4bc5bbb..a5b73b2ba 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -1077,6 +1077,9 @@ class DivEngine { // rescan audio devices void rescanAudioDevices(); + /** rescan midi devices */ + void rescanMidiDevices(); + // set the console mode. void setConsoleMode(bool enable); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 8c503d72b..df0ca64eb 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1129,6 +1129,13 @@ void FurnaceGUI::drawSettings() { ImGui::EndCombo(); } + ImGui::SameLine(); + if (ImGui::Button("Reload MIDI devices")) { + e->rescanMidiDevices(); + audioEngineChanged=true; + settingsChanged=false; + } + if (hasToReloadMidi) { midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); midiMap.compile(); From 049088f77c00fb5b1e230546bae6367a6c112e4f Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Wed, 27 Dec 2023 09:10:28 +0100 Subject: [PATCH 50/53] Fix return of reinitMidi --- src/audio/midi.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/audio/midi.cpp b/src/audio/midi.cpp index ea46cc20e..3da138868 100644 --- a/src/audio/midi.cpp +++ b/src/audio/midi.cpp @@ -62,5 +62,6 @@ void TAAudio::quitMidi() { bool TAAudio::reinitMidi(bool jack) { quitMidi(); - initMidi(jack); + return initMidi(jack); + } \ No newline at end of file From 3039ce24164a5ebb190f3627d3479a78eac8fb5b Mon Sep 17 00:00:00 2001 From: Daniel Konar Date: Thu, 28 Dec 2023 10:11:30 +0100 Subject: [PATCH 51/53] Change reload to re-scan and refactor Remove rt midi reinitialization (not needed as per docs) Move midi rescan code to rescanMidiDevices as MIDI is not audio (rescanAudioDevices also appears to be unused) --- src/audio/midi.cpp | 6 ------ src/audio/taAudio.h | 2 -- src/engine/engine.cpp | 15 ++++++--------- src/gui/settings.cpp | 2 +- 4 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/audio/midi.cpp b/src/audio/midi.cpp index 3da138868..fa3faf1f4 100644 --- a/src/audio/midi.cpp +++ b/src/audio/midi.cpp @@ -58,10 +58,4 @@ void TAAudio::quitMidi() { delete midiOut; midiOut=NULL; } -} - -bool TAAudio::reinitMidi(bool jack) { - quitMidi(); - return initMidi(jack); - } \ No newline at end of file diff --git a/src/audio/taAudio.h b/src/audio/taAudio.h index 3d21cbaff..0b6f530c6 100644 --- a/src/audio/taAudio.h +++ b/src/audio/taAudio.h @@ -178,8 +178,6 @@ class TAAudio { virtual std::vector listAudioDevices(); bool initMidi(bool jack); void quitMidi(); - /** remove and reload midi to allow hotswaping midi devices */ - bool reinitMidi(bool jack); virtual bool init(TAAudioDesc& request, TAAudioDesc& response); TAAudio(): diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 76c56cb16..fc5d45477 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3464,6 +3464,12 @@ void DivEngine::rescanAudioDevices() { audioDevs.clear(); if (output!=NULL) { audioDevs=output->listAudioDevices(); + } +} + +void DivEngine::rescanMidiDevices() { + if (output!=NULL) { + logV("re-scanning midi..."); if (output->midiIn!=NULL) { midiIns=output->midiIn->listDevices(); } @@ -3473,15 +3479,6 @@ void DivEngine::rescanAudioDevices() { } } - -void DivEngine::rescanMidiDevices() { - if (output!=NULL) { - logV("reloading midi..."); - output->reinitMidi(false); - rescanAudioDevices(); - } -} - void DivEngine::initDispatch(bool isRender) { BUSY_BEGIN; logV("initializing dispatch..."); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index df0ca64eb..3f4bd9de7 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1130,7 +1130,7 @@ void FurnaceGUI::drawSettings() { } ImGui::SameLine(); - if (ImGui::Button("Reload MIDI devices")) { + if (ImGui::Button("Re-scan MIDI devices")) { e->rescanMidiDevices(); audioEngineChanged=true; settingsChanged=false; From 673e3246d21fb42e8e1e00869166bef7d1fb4dd6 Mon Sep 17 00:00:00 2001 From: nk <98922449+LoKiToon@users.noreply.github.com> Date: Thu, 28 Dec 2023 22:02:52 +0300 Subject: [PATCH 52/53] Add files via upload --- demos/misc/fragments_turbosound.fur | Bin 0 -> 2114 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/misc/fragments_turbosound.fur diff --git a/demos/misc/fragments_turbosound.fur b/demos/misc/fragments_turbosound.fur new file mode 100644 index 0000000000000000000000000000000000000000..61064a7fcbe4dad65d2d8eb925e1f4da56ce1080 GIT binary patch literal 2114 zcmV-I2)*}soZVPkY+Oef{^wkF*T;Kt9NU=_$MG4c1juZYI22Nqs4W=o2DsL;sqWM0)Zm+tqLH-YlQk-79qq_-w;oL`Q|?t zZ(=*aQd6ZI=bM@T{{J&`X3i!*RIi;H8!vj5>4|!|nE#N7JX(1U3=fTbU}BI+h~2v{ zeQO`JlhxM3vM&M)3-rKe-$R}UTNC{mvA=FTOzY7-dp&QoFnGLg=*5BO4-F2F7Wzhp z`Ud(6qeu6H`ck3q@ZfOY@qw2-Z?D$PzdV~ibzo{F?>)2M%X_=}cl7Vs(Z5?uY2oAN zh=kBygEko5L3I~wqOJtdJ3uW-^iNpmcqzX-zvt;)J9$k9U&t4}Ju-4I zKX~jguTHbO`^Ha1YsF66&|>Q7?y>+3zyvJ71`;`b@D_oZfq;R*%Mo zVs-DX{->Qs<#u5Iffw6&qVcrwrm*ZpGNt*^8KB3>NJpPfh5kF}KcoZ=546x&t?HGh zr>it{=n47<1Up~r?cJAKzdtlkBD`e@T82v_n|3lzQlywN$xNDAa>(2l&<5`;{25^% zkDIZEJk(h(9I|M6nR*ZIM+u&N*uEo%2T{r3u&(1=Tt|jxily3wS1s4ar)D=$%N|`z z8$y`KH@8eZP49|nE_00*841Bdfl`BkwcM0akj<-cHa1Pv>Xi*-(a$p)M9bx}nKa%? zP`@Ry8#jVwh-KlE_}Ra!|A`Vwt&aau96!gas&hEK(Y2j<=-L{ARP^?Cc5cmlMk_tn zw9+Tar8DCvXT5T9Y+?hIJ*g`*gUWK5Rx>OTuZd~I>zj_&*IYfn-)Vkp>Sw0C;;C0Q z5I>Fhk#}RY_sQ6MnkL7liry>J<$9&KfdDQ7MBC<7y=??4#J2gdSRL)aCYy@;_$%e za3+4IOZ2`uNz0nqLBY)zF>gtucOhl4EFWHAWY$yXfkm^FTH;=#23{@YGbTY#rr4-6 z2oXEHAsw+Ob0aKFg$_yFW_mHYhm&(wjznF|+x0Aod9i3^LNd*yJ?H4YZu_7Y!C!=Y z4)i?Keb6&7uZmf_3VK$|YjY;kOxiP!?yL43yPLJ=A)f&~3-!6CRFHSFs#7V6nq7jh zCT6rRCeuvXRY&(FPHhJK8ID!5YgwIg&7Ok!gs9jPpev%L&6!LyX-_%2pWxJL;MX9Z z0$qW6l~ehMQ^A_#%0|w^L<+MeTFZuK*7eAo35cp#Pr_`pOfWlA15u47ff^(y3`Cg_ zW`&1ZqZ0AWDY2e}*=U(ycBBSkDwf0wO|@B{-`dIs%s9jesZw*mYUm#kuz(BO(psB|06I!%pk!y&eDg)8lWd^df3QgR*h>xtyNaa&5Q&XYu zsi1!*SGJ9vj#&7zZ8%)$>F|jC6warX@IXq6+k7J3wl(9nT{S`8re||UY~dMBn-pCf z`VSmB`T3q>oc&zPrtp%X?h<_>-tV47+awTLV6wmr1^k;)f0#50n(wBaEI0H`;)mCh z&nWRV<%idkzLu7_uLalBlWd1${)Zc};!Dl2@5$yyR9rf|@Mur&3%~MA=KX?sx~|cL zXOi?RxlY&I{;zsy=>gsPdh4~E^G`Tu<8jWJ^Sl}SE_vMYTnfKw_#4Ms`NsJs)7T2V zXOoV#LmJkCSn|a=U!3>F1z%kB#p}MfDP1Y8TL$bC-{IxhEPE*I}A0%@lE zBi)6X1~iS>iv=%M93g=Si8y)+(Nl3|4l;A%tkcLk9aoe^pln=i8v?b(GtrJf?eQ#i zAW%mA&&au7%R-P0^TqCNQSId>;5LfYMEVt09I&*(U7&eiLk=4kA^J! zSUBJ;e!y86pLrHdTjH0Tf)%8&t<^Kq84v!qWjrJ0X>AEstEa6rkB1M? Date: Fri, 29 Dec 2023 02:16:55 -0500 Subject: [PATCH 53/53] pull request #1663 but optimized --- demos/nes/christmas-fever.fur | Bin 0 -> 63881 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/nes/christmas-fever.fur diff --git a/demos/nes/christmas-fever.fur b/demos/nes/christmas-fever.fur new file mode 100644 index 0000000000000000000000000000000000000000..87453024201dcb2256f0ef6c590dcf3d64a323c3 GIT binary patch literal 63881 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]BXQzVi6dz9YTeqa#4O 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 literal 0 HcmV?d00001