diff --git a/TODO.md b/TODO.md index b08c462b3..3fa7f4b99 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,6 @@ # to-do for 0.6.4 +- revamp audio export dialog - fix possible issues when moving selection - fix Metal intro crash @@ -7,3 +8,5 @@ - finish auto-clone - new pattern renderer - performance improvements +- new info header +- unlimited channels and chips diff --git a/src/engine/engine.h b/src/engine/engine.h index fa01495d1..00789b916 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -98,6 +98,28 @@ enum DivMIDIModes { DIV_MIDI_MODE_LIGHT_SHOW }; +struct DivAudioExportOptions { + DivAudioExportModes mode; + int sampleRate; + int chans; + int loops; + double fadeOut; + int orderBegin, orderEnd; + bool channelMask[DIV_MAX_CHANS]; + DivAudioExportOptions(): + mode(DIV_EXPORT_MODE_ONE), + sampleRate(44100), + chans(2), + loops(0), + fadeOut(0.0), + orderBegin(-1), + orderEnd(-1) { + for (int i=0; i delayed; int note, oldNote, lastIns, pitch, portaSpeed, portaNote; @@ -457,6 +479,7 @@ class DivEngine { DivAudioEngines audioEngine; DivAudioExportModes exportMode; double exportFadeOut; + int exportOutputs; DivConfig conf; FixedQueue pendingNotes; // bitfield @@ -670,7 +693,7 @@ class DivEngine { // export to text SafeWriter* saveText(bool separatePatterns=true); // export to an audio file - bool saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime=0.0); + bool saveAudio(const char* path, DivAudioExportOptions options); // wait for audio export to finish void waitAudioFile(); // stop audio file export @@ -1360,6 +1383,7 @@ class DivEngine { audioEngine(DIV_AUDIO_NULL), exportMode(DIV_EXPORT_MODE_ONE), exportFadeOut(0.0), + exportOutputs(2), cmdStreamInt(NULL), midiBaseChan(0), midiPoly(true), diff --git a/src/engine/wavOps.cpp b/src/engine/wavOps.cpp index edf4a8885..dba9e4b3c 100644 --- a/src/engine/wavOps.cpp +++ b/src/engine/wavOps.cpp @@ -45,7 +45,7 @@ void DivEngine::runExportThread() { SF_INFO si; SFWrapper sfWrap; si.samplerate=got.rate; - si.channels=2; + si.channels=exportOutputs; si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; sf=sfWrap.doOpen(exportPath.c_str(),SFM_WRITE,&si); @@ -55,10 +55,12 @@ void DivEngine::runExportThread() { return; } - float* outBuf[3]; - outBuf[0]=new float[EXPORT_BUFSIZE]; - outBuf[1]=new float[EXPORT_BUFSIZE]; - outBuf[2]=new float[EXPORT_BUFSIZE*2]; + float* outBuf[DIV_MAX_OUTPUTS]; + float* outBufFinal; + for (int i=0; iEXPORT_BUFSIZE) { logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); totalProcessed=EXPORT_BUFSIZE; } + int fi=0; for (int i=0; i<(int)totalProcessed; i++) { total++; if (isFadingOut) { double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); - outBuf[2][i<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][i]))*mul; - outBuf[2][1+(i<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][i]))*mul; + for (int j=0; j=fadeOutSamples) { playing=false; break; } } else { - outBuf[2][i<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][i])); - outBuf[2][1+(i<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][i])); + for (int j=0; j-1 && i>=lastLoopPos && totalLoops>=exportLoopCount) { logD("start fading out..."); isFadingOut=true; @@ -94,15 +99,16 @@ void DivEngine::runExportThread() { } } - if (sf_writef_float(sf,outBuf[2],total)!=(int)total) { + if (sf_writef_float(sf,outBufFinal,total)!=(int)total) { logE("error: failed to write entire buffer!"); break; } } - delete[] outBuf[0]; - delete[] outBuf[1]; - delete[] outBuf[2]; + delete[] outBufFinal; + for (int i=0; iDIV_MAX_OUTPUTS) exportOutputs=DIV_MAX_OUTPUTS; + + exportLoopCount=options.loops+1; exportThread=new std::thread(_runExportThread,this); return true; #endif diff --git a/src/gui/exportOptions.cpp b/src/gui/exportOptions.cpp index 5bbf84a78..5274115c6 100644 --- a/src/gui/exportOptions.cpp +++ b/src/gui/exportOptions.cpp @@ -26,14 +26,35 @@ void FurnaceGUI::drawExportAudio(bool onWindow) { 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; + 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::InputDouble("Fade out (seconds)",&exportFadeOut,1.0,2.0,"%.1f")) { - if (exportFadeOut<0.0) exportFadeOut=0.0; + if (ImGui::RadioButton("multiple files (one per chip)",audioExportOptions.mode==DIV_EXPORT_MODE_MANY_SYS)) { + audioExportOptions.mode=DIV_EXPORT_MODE_MANY_SYS; + } + 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 (ImGui::InputInt("Sample rate",&audioExportOptions.sampleRate,100,10000)) { + if (audioExportOptions.sampleRate<8000) audioExportOptions.sampleRate=8000; + if (audioExportOptions.sampleRate>384000) audioExportOptions.sampleRate=384000; + } + + 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 (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; } if (onWindow) { @@ -43,14 +64,14 @@ void FurnaceGUI::drawExportAudio(bool onWindow) { } if (ImGui::Button("Export",ImVec2(200.0f*dpiScale,0))) { - switch (audioExportType) { - case 0: + switch (audioExportOptions.mode) { + case DIV_EXPORT_MODE_ONE: openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); break; - case 1: + case DIV_EXPORT_MODE_MANY_SYS: openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); break; - case 2: + case DIV_EXPORT_MODE_MANY_CHAN: openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL); break; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 5a5c36467..4cfc563ce 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2436,7 +2436,7 @@ int FurnaceGUI::loadStream(String path) { void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) { - e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut); + e->saveAudio(path.c_str(),audioExportOptions); displayExporting=true; } @@ -6761,10 +6761,10 @@ bool FurnaceGUI::init() { followOrders=e->getConfBool("followOrders",true); followPattern=e->getConfBool("followPattern",true); noteInputPoly=e->getConfBool("noteInputPoly",true); - exportLoops=e->getConfInt("exportLoops",0); - if (exportLoops<0) exportLoops=0; - exportFadeOut=e->getConfDouble("exportFadeOut",0.0); - if (exportFadeOut<0.0) exportFadeOut=0.0; + audioExportOptions.loops=e->getConfInt("exportLoops",0); + if (audioExportOptions.loops<0) audioExportOptions.loops=0; + audioExportOptions.fadeOut=e->getConfDouble("exportFadeOut",0.0); + if (audioExportOptions.fadeOut<0.0) audioExportOptions.fadeOut=0.0; orderEditMode=e->getConfInt("orderEditMode",0); if (orderEditMode<0) orderEditMode=0; if (orderEditMode>3) orderEditMode=3; @@ -6826,8 +6826,8 @@ bool FurnaceGUI::init() { syncTutorial(); if (!settings.persistFadeOut) { - exportLoops=settings.exportLoops; - exportFadeOut=settings.exportFadeOut; + audioExportOptions.loops=settings.exportLoops; + audioExportOptions.fadeOut=settings.exportFadeOut; } for (int i=0; isetConf("orderEditMode",orderEditMode); e->setConf("noteInputPoly",noteInputPoly); if (settings.persistFadeOut) { - e->setConf("exportLoops",exportLoops); - e->setConf("exportFadeOut",exportFadeOut); + e->setConf("exportLoops",audioExportOptions.loops); + e->setConf("exportFadeOut",audioExportOptions.fadeOut); } // commit oscilloscope state @@ -7581,7 +7581,6 @@ FurnaceGUI::FurnaceGUI(): oldRow(0), editStep(1), editStepCoarse(16), - exportLoops(0), soloChan(-1), orderEditMode(0), orderCursor(-1), @@ -7604,7 +7603,6 @@ FurnaceGUI::FurnaceGUI(): curPaletteChoice(0), curPaletteType(0), soloTimeout(0.0f), - exportFadeOut(5.0), patExtraButtons(false), patChannelNames(false), patChannelPairs(true), @@ -7955,7 +7953,6 @@ FurnaceGUI::FurnaceGUI(): introStopped(false), curTutorial(-1), curTutorialStep(0), - audioExportType(0), dmfExportVersion(0), curExportType(GUI_EXPORT_NONE) { // value keys diff --git a/src/gui/gui.h b/src/gui/gui.h index 90e181259..e29c7e3b6 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2194,15 +2194,13 @@ class FurnaceGUI { int pendingLayoutImportStep; FixedQueue pendingLayoutImportReopen; - int curIns, curWave, curSample, curOctave, curOrder, playOrder, prevIns, oldRow, editStep, editStepCoarse, exportLoops, soloChan, orderEditMode, orderCursor; + int curIns, curWave, curSample, curOctave, curOrder, playOrder, prevIns, oldRow, editStep, editStepCoarse, soloChan, orderEditMode, orderCursor; int loopOrder, loopRow, loopEnd, isClipping, newSongCategory, latchTarget; int wheelX, wheelY, dragSourceX, dragSourceXFine, dragSourceY, dragDestinationX, dragDestinationXFine, dragDestinationY, oldBeat, oldBar; int curGroove, exitDisabledTimer; int curPaletteChoice, curPaletteType; float soloTimeout; - double exportFadeOut; - bool patExtraButtons, patChannelNames, patChannelPairs; unsigned char patChannelHints; @@ -2603,7 +2601,7 @@ class FurnaceGUI { ImGuiListClipper csClipper; // export options - int audioExportType; + DivAudioExportOptions audioExportOptions; int dmfExportVersion; FurnaceGUIExportTypes curExportType; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 0e4684bee..d3a64033e 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -705,13 +705,13 @@ void FurnaceGUI::drawSettings() { ImGui::BeginDisabled(settings.persistFadeOut); ImGui::Indent(); if (ImGui::InputInt("Loops",&settings.exportLoops,1,2)) { - if (exportLoops<0) exportLoops=0; - exportLoops=settings.exportLoops; + if (settings.exportLoops<0) settings.exportLoops=0; + audioExportOptions.loops=settings.exportLoops; settingsChanged=true; } if (ImGui::InputDouble("Fade out (seconds)",&settings.exportFadeOut,1.0,2.0,"%.1f")) { - if (exportFadeOut<0.0) exportFadeOut=0.0; - exportFadeOut=settings.exportFadeOut; + if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0; + audioExportOptions.fadeOut=settings.exportFadeOut; settingsChanged=true; } ImGui::Unindent(); diff --git a/src/main.cpp b/src/main.cpp index f635955ed..7254ddcc4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -66,10 +66,9 @@ String outName; String vgmOutName; String zsmOutName; String cmdOutName; -int loops=1; int benchMode=0; int subsong=-1; -DivAudioExportModes outMode=DIV_EXPORT_MODE_ONE; +DivAudioExportOptions exportOptions; #ifdef HAVE_GUI bool consoleMode=false; @@ -299,9 +298,9 @@ TAParamResult pLoops(String val) { try { int count=std::stoi(val); if (count<0) { - loops=0; + exportOptions.loops=0; } else { - loops=count+1; + exportOptions.loops=count; } } catch (std::exception& e) { logE("loop count shall be a number."); @@ -327,11 +326,11 @@ TAParamResult pSubSong(String val) { TAParamResult pOutMode(String val) { if (val=="one") { - outMode=DIV_EXPORT_MODE_ONE; + exportOptions.mode=DIV_EXPORT_MODE_ONE; } else if (val=="persys") { - outMode=DIV_EXPORT_MODE_MANY_SYS; + exportOptions.mode=DIV_EXPORT_MODE_MANY_SYS; } else if (val=="perchan") { - outMode=DIV_EXPORT_MODE_MANY_CHAN; + exportOptions.mode=DIV_EXPORT_MODE_MANY_CHAN; } else { logE("invalid value for outmode! valid values are: one, persys and perchan."); return TA_PARAM_ERROR; @@ -401,7 +400,7 @@ void initParams() { params.push_back(TAParam("n","nostatus",false,pNoStatus,"","disable playback status in console mode")); params.push_back(TAParam("N","nocontrols",false,pNoControls,"","disable standard input controls in console mode")); - params.push_back(TAParam("l","loops",true,pLoops,"","set number of loops (-1 means loop forever)")); + params.push_back(TAParam("l","loops",true,pLoops,"","set number of loops")); params.push_back(TAParam("s","subsong",true,pSubSong,"","set sub-song")); params.push_back(TAParam("o","outmode",true,pOutMode,"one|persys|perchan","set file output mode")); params.push_back(TAParam("S","safemode",false,pSafeMode,"","enable safe mode (software rendering and no audio)")); @@ -716,7 +715,7 @@ int main(int argc, char** argv) { } if (outName!="") { e.setConsoleMode(true); - e.saveAudio(outName.c_str(),loops,outMode); + e.saveAudio(outName.c_str(),exportOptions); e.waitAudioFile(); } finishLogFile();