diff --git a/TODO.md b/TODO.md index 5bd4e8925..edf0caf65 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,6 @@ # to-do for 0.6pre1 +- fade out in audio export - rewrite the system name detection function anyway - add another FM editor layout - add ability to move selection by dragging diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 093f6d942..5c73817ac 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -193,6 +193,9 @@ bool DivEngine::isExporting() { #ifdef HAVE_SNDFILE void DivEngine::runExportThread() { + size_t fadeOutSamples=got.rate*exportFadeOut; + size_t curFadeOutSample=0; + bool isFadingOut=false; switch (exportMode) { case DIV_EXPORT_MODE_ONE: { SNDFILE* sf; @@ -220,15 +223,36 @@ void DivEngine::runExportThread() { logI("rendering to file..."); while (playing) { + size_t total=0; nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); - for (int i=0; iEXPORT_BUFSIZE) { logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); + totalProcessed=EXPORT_BUFSIZE; } - if (sf_writef_float(sf,outBuf[2],totalProcessed)!=(int)totalProcessed) { + if (totalProcessed!=EXPORT_BUFSIZE) { + logW("wait what? %d != %d",totalProcessed,EXPORT_BUFSIZE); + } + 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; + if (++curFadeOutSample>=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])); + if (lastLoopPos>-1 && i>=lastLoopPos && totalLoops>=exportLoopCount) { + logD("start fading out..."); + isFadingOut=true; + } + } + } + + if (sf_writef_float(sf,outBuf[2],total)!=(int)total) { logE("error: failed to write entire buffer!"); break; } @@ -382,7 +406,12 @@ void DivEngine::runExportThread() { } curOrder=0; - remainingLoops=loopCount; + prevOrder=0; + if (exportFadeOut<=0.01) { + remainingLoops=loopCount; + } else { + remainingLoops=-1; + } playSub(false); while (playing) { @@ -448,13 +477,14 @@ void DivEngine::runExportThread() { } #endif -bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode) { +bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime) { #ifndef HAVE_SNDFILE logE("Furnace was not compiled with libsndfile. cannot export!"); return false; #else exportPath=path; exportMode=mode; + exportFadeOut=fadeOutTime; if (exportMode!=DIV_EXPORT_MODE_ONE) { // remove extension String lowerCase=exportPath; @@ -471,7 +501,12 @@ bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode) stop(); repeatPattern=false; setOrder(0); - remainingLoops=loops; + if (exportFadeOut<=0.01) { + remainingLoops=loops; + } else { + remainingLoops=-1; + } + exportLoopCount=loops; exportThread=new std::thread(_runExportThread,this); return true; #endif @@ -1131,6 +1166,8 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { totalTicks=0; totalSeconds=0; totalTicksR=0; + totalLoops=0; + lastLoopPos=-1; } speedAB=false; playing=true; diff --git a/src/engine/engine.h b/src/engine/engine.h index ec48e187a..dc16584de 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -303,7 +303,7 @@ class DivEngine { bool systemsRegistered; bool hasLoadedSomething; int softLockCount; - int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, nextSpeed; + int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed; size_t curSubSongIndex; double divider; int cycles; @@ -318,6 +318,7 @@ class DivEngine { DivChannelState chan[DIV_MAX_CHANS]; DivAudioEngines audioEngine; DivAudioExportModes exportMode; + double exportFadeOut; std::map conf; std::queue pendingNotes; bool isMuted[DIV_MAX_CHANS]; @@ -463,7 +464,7 @@ class DivEngine { // dump to VGM. SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171); // export to an audio file - bool saveAudio(const char* path, int loops, DivAudioExportModes mode); + bool saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime=0.0); // wait for audio export to finish void waitAudioFile(); // stop audio file export @@ -938,6 +939,9 @@ class DivEngine { prevRow(0), prevOrder(0), remainingLoops(-1), + totalLoops(0), + lastLoopPos(0), + exportLoopCount(0), nextSpeed(3), curSubSongIndex(0), divider(60), @@ -961,6 +965,7 @@ class DivEngine { haltOn(DIV_HALT_NONE), audioEngine(DIV_AUDIO_NULL), exportMode(DIV_EXPORT_MODE_ONE), + exportFadeOut(0.0), midiBaseChan(0), midiPoly(true), midiAgeCounter(0), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index f7c1646f9..ed70da94b 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1096,6 +1096,8 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size) { + lastLoopPos=-1; + if (out!=NULL) { memset(out[0],0,size*sizeof(float)); memset(out[1],0,size*sizeof(float)); @@ -1287,6 +1289,9 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi } } if (nextTick()) { + lastLoopPos=size-(runLeftG>>MASTER_CLOCK_PREC); + logD("last loop pos: %d for a size of %d and runLeftG of %d",lastLoopPos,size,runLeftG); + totalLoops++; if (remainingLoops>0) { remainingLoops--; if (!remainingLoops) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 309d605b4..6e4641844 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1676,7 +1676,7 @@ int FurnaceGUI::load(String path) { } void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) { - e->saveAudio(path.c_str(),exportLoops+1,mode); + e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut); displayExporting=true; } @@ -2827,6 +2827,9 @@ bool FurnaceGUI::loop() { 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...")) { @@ -4351,6 +4354,7 @@ FurnaceGUI::FurnaceGUI(): latchTarget(0), wheelX(0), wheelY(0), + exportFadeOut(5.0), editControlsOpen(true), ordersOpen(true), insListOpen(true), diff --git a/src/gui/gui.h b/src/gui/gui.h index 86301b0a9..7001c88e9 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1072,6 +1072,8 @@ class FurnaceGUI { int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory, latchTarget; int wheelX, wheelY; + double exportFadeOut; + bool editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen; bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen;