/** * Furnace Tracker - multi-system chiptune tracker * Copyright (C) 2021-2025 tildearrow and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "gui.h" #include "guiConst.h" #include "../fileutils.h" #include "misc/cpp/imgui_stdlib.h" #include void FurnaceGUI::drawExportAudio(bool onWindow) { exitDisabledTimer=1; ImGui::Text(_("Export type:")); ImGui::Indent(); if (ImGui::RadioButton(_("one file"),audioExportOptions.mode==DIV_EXPORT_MODE_ONE)) { audioExportOptions.mode=DIV_EXPORT_MODE_ONE; } if (ImGui::RadioButton(_("multiple files (one per chip)"),audioExportOptions.mode==DIV_EXPORT_MODE_MANY_SYS)) { audioExportOptions.mode=DIV_EXPORT_MODE_MANY_SYS; audioExportOptions.format=DIV_EXPORT_FORMAT_S16; } if (ImGui::RadioButton(_("multiple files (one per channel)"),audioExportOptions.mode==DIV_EXPORT_MODE_MANY_CHAN)) { audioExportOptions.mode=DIV_EXPORT_MODE_MANY_CHAN; } ImGui::Unindent(); if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_SYS) { ImGui::Text(_("File format:")); ImGui::Indent(); if (ImGui::RadioButton(_("Wave (16-bit integer)"),audioExportOptions.format==DIV_EXPORT_FORMAT_S16)) { audioExportOptions.format=DIV_EXPORT_FORMAT_S16; } if (ImGui::RadioButton(_("Wave (32-bit float)"),audioExportOptions.format==DIV_EXPORT_FORMAT_F32)) { audioExportOptions.format=DIV_EXPORT_FORMAT_F32; } if (supportsOgg) { if (ImGui::RadioButton(_("Opus"),audioExportOptions.format==DIV_EXPORT_FORMAT_OPUS)) { audioExportOptions.format=DIV_EXPORT_FORMAT_OPUS; } if (ImGui::RadioButton(_("FLAC (Free Lossless Audio Codec)"),audioExportOptions.format==DIV_EXPORT_FORMAT_FLAC)) { audioExportOptions.format=DIV_EXPORT_FORMAT_FLAC; } if (ImGui::RadioButton(_("Vorbis"),audioExportOptions.format==DIV_EXPORT_FORMAT_VORBIS)) { audioExportOptions.format=DIV_EXPORT_FORMAT_VORBIS; } } if (supportsMP3) { if (ImGui::RadioButton(_("MP3"),audioExportOptions.format==DIV_EXPORT_FORMAT_MPEG_L3)) { audioExportOptions.format=DIV_EXPORT_FORMAT_MPEG_L3; } } ImGui::Unindent(); } bool rateCheck=( audioExportOptions.format==DIV_EXPORT_FORMAT_OPUS && ( audioExportOptions.sampleRate!=8000 && audioExportOptions.sampleRate!=12000 && audioExportOptions.sampleRate!=16000 && audioExportOptions.sampleRate!=24000 && audioExportOptions.sampleRate!=48000 ) ); pushWarningColor(false,rateCheck); if (ImGui::InputInt(_("Sample rate"),&audioExportOptions.sampleRate,100,10000)) { if (audioExportOptions.sampleRate<8000) audioExportOptions.sampleRate=8000; if (audioExportOptions.sampleRate>384000) audioExportOptions.sampleRate=384000; } if (rateCheck) { ImGui::SetItemTooltip(_("Opus only supports the following sample rates: 8000, 12000, 16000, 24000 and 48000.")); } popWarningColor(); if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_SYS) { if (ImGui::InputInt(_("Channels in file"),&audioExportOptions.chans,1,1)) { if (audioExportOptions.chans<1) audioExportOptions.chans=1; if (audioExportOptions.chans>16) audioExportOptions.chans=16; } } if (audioExportOptions.format==DIV_EXPORT_FORMAT_MPEG_L3) { ImGui::Text(_("Bit rate mode:")); ImGui::Indent(); if (ImGui::RadioButton(_("Constant"),audioExportOptions.bitRateMode==DIV_EXPORT_BITRATE_CONSTANT)) { audioExportOptions.bitRateMode=DIV_EXPORT_BITRATE_CONSTANT; } if (ImGui::RadioButton(_("Variable"),audioExportOptions.bitRateMode==DIV_EXPORT_BITRATE_VARIABLE)) { audioExportOptions.bitRateMode=DIV_EXPORT_BITRATE_VARIABLE; } if (ImGui::RadioButton(_("Average"),audioExportOptions.bitRateMode==DIV_EXPORT_BITRATE_AVERAGE)) { audioExportOptions.bitRateMode=DIV_EXPORT_BITRATE_AVERAGE; } ImGui::Unindent(); } int minBitRate=6000; int maxBitRate=256000; if (audioExportOptions.format==DIV_EXPORT_FORMAT_MPEG_L3) { if (audioExportOptions.sampleRate>=32000) { minBitRate=32000; maxBitRate=320000; } else if (audioExportOptions.sampleRate>=16000) { minBitRate=8000; maxBitRate=160000; } else { minBitRate=8000; maxBitRate=64000; } } if (audioExportOptions.format!=DIV_EXPORT_FORMAT_S16 && audioExportOptions.format!=DIV_EXPORT_FORMAT_F32) { if (audioExportOptions.format==DIV_EXPORT_FORMAT_FLAC) { if (ImGui::SliderFloat(_("Compression level"),&audioExportOptions.vbrQuality,0,8)) { if (audioExportOptions.vbrQuality<0) audioExportOptions.vbrQuality=0; if (audioExportOptions.vbrQuality>8) audioExportOptions.vbrQuality=8; } } else if (audioExportOptions.format==DIV_EXPORT_FORMAT_VORBIS || (audioExportOptions.format==DIV_EXPORT_FORMAT_MPEG_L3 && audioExportOptions.bitRateMode==DIV_EXPORT_BITRATE_VARIABLE)) { if (ImGui::SliderFloat(_("Quality"),&audioExportOptions.vbrQuality,0,10)) { if (audioExportOptions.vbrQuality<0) audioExportOptions.vbrQuality=0; if (audioExportOptions.vbrQuality>10) audioExportOptions.vbrQuality=10; } } else { if (ImGui::InputInt(_("Bit rate"),&audioExportOptions.bitRate,1000,10000)) { } if (audioExportOptions.bitRatemaxBitRate) audioExportOptions.bitRate=maxBitRate; } } if (ImGui::InputInt(_("Loops"),&audioExportOptions.loops,1,2)) { if (audioExportOptions.loops<0) audioExportOptions.loops=0; } if (ImGui::InputDouble(_("Fade out (seconds)"),&audioExportOptions.fadeOut,1.0,2.0,"%.1f")) { if (audioExportOptions.fadeOut<0.0) audioExportOptions.fadeOut=0.0; } bool isOneOn=false; if (audioExportOptions.mode==DIV_EXPORT_MODE_MANY_CHAN) { ImGui::Text(_("Channels to export:")); ImGui::SameLine(); if (ImGui::SmallButton(_("All"))) { for (int i=0; icurSubSong->chanShow[i]; } } ImGui::SameLine(); if (ImGui::SmallButton(_("Shown in oscilloscope"))) { for (int i=0; icurSubSong->chanShowChanOsc[i]; } } ImGui::SameLine(); if (ImGui::SmallButton(_("Invert"))) { for (int i=0; igetTotalChannelCount(); i++) { String name=fmt::sprintf("%d. %s##_CE%d",i+1,e->getChannelName(i),i); ImGui::Checkbox(name.c_str(),&audioExportOptions.channelMask[i]); if (audioExportOptions.channelMask[i]) isOneOn=true; } } ImGui::EndChild(); } else { isOneOn=true; } if (onWindow) { ImGui::Separator(); if (ImGui::Button(_("Cancel"),ImVec2(200.0f*dpiScale,0))) ImGui::CloseCurrentPopup(); ImGui::SameLine(); } if (isOneOn && !rateCheck) { if (ImGui::Button(_("Export"),ImVec2(200.0f*dpiScale,0))) { switch (audioExportOptions.format) { case DIV_EXPORT_FORMAT_S16: case DIV_EXPORT_FORMAT_F32: audioExportFilterName=_("Wave file"); audioExportFilterExt=".wav"; break; case DIV_EXPORT_FORMAT_OPUS: case DIV_EXPORT_FORMAT_VORBIS: audioExportFilterName=_("Ogg files"); audioExportFilterExt=".ogg"; break; case DIV_EXPORT_FORMAT_FLAC: audioExportFilterName=_("FLAC files"); audioExportFilterExt=".flac"; break; case DIV_EXPORT_FORMAT_MPEG_L3: audioExportFilterName=_("MPEG Layer 3 files"); audioExportFilterExt=".mp3"; break; default: audioExportFilterName=_("all files"); audioExportFilterExt="*"; break; } switch (audioExportOptions.mode) { case DIV_EXPORT_MODE_ONE: openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); break; case DIV_EXPORT_MODE_MANY_SYS: openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); break; case DIV_EXPORT_MODE_MANY_CHAN: openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL); break; } ImGui::CloseCurrentPopup(); } } else { if (rateCheck) { ImGui::Text(_("check sample rate")); } else { ImGui::Text(_("select at least one channel")); } } } void FurnaceGUI::drawExportVGM(bool onWindow) { 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; bool hasNES=false; for (int i=0; isong.systemLen; i++) { int minVersion=e->minVGMVersion(e->song.system[i]); if (e->song.system[i]==DIV_SYSTEM_NES) hasNES=true; 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 (hasNES) { ImGui::Text(_("NES DPCM bank switch method:")); if (ImGui::RadioButton(_("data blocks"),!vgmExportDPCM07)) { vgmExportDPCM07=false; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("67 66 C2 - writes a new data block on each bank switch.\nmay result in bigger files but is compatible with all players.")); } if (ImGui::RadioButton(_("RAM write commands"),vgmExportDPCM07)) { vgmExportDPCM07=true; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(_("67 66 07 - uses RAM write commands (68) to switch banks.\nnot all VGM players support this!")); } } ImGui::Text(_("speed drift compensation:")); if (ImGui::RadioButton(_("none"),vgmExportCorrectedRate==44100)) { vgmExportCorrectedRate=44100; } // as tested on a Model 1 Genesis (VA6, USA): // 0.97841613336995507871 slower, 1.02206000687632131440 longer if (ImGui::RadioButton(_("DeadFish VgmPlay (1.02×)"),vgmExportCorrectedRate==43148)) { vgmExportCorrectedRate=43148; } if (hasOneAtLeast) { 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::drawExportROM(bool onWindow) { exitDisabledTimer=1; const DivROMExportDef* def=e->getROMExportDef(romTarget); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::BeginCombo("##ROMTarget",def==NULL?"