/** * 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 "engine.h" #include "../ta-log.h" #ifdef HAVE_SNDFILE #include "sfWrapper.h" #endif #define EXPORT_BUFSIZE 2048 void _runExportThread(DivEngine* caller) { caller->runExportThread(); } bool DivEngine::isExporting() { return exporting; } void DivEngine::getLoopsLeft(int &loops) { if (totalLoops<0 || exportLoopCount==0) { loops=0; return; } loops=exportLoopCount-1-totalLoops; } void DivEngine::getTotalLoops(int &loops) { loops=exportLoopCount-1; } void DivEngine::getCurSongPos(int &row, int &order) { row=curRow; order=curOrder; } void DivEngine::getTotalAudioFiles(int &files) { files=0; switch (exportMode) { case DIV_EXPORT_MODE_ONE: { files=1; break; } case DIV_EXPORT_MODE_MANY_SYS: { files=1; // there actually are several files but they are processed in the same loop, so to correctly draw progress we think of them as one file break; } case DIV_EXPORT_MODE_MANY_CHAN: { 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)); for (int j=0; j=fadeOutSamples) { playing=false; break; } } else { for (int j=0; j-1 && i>=lastLoopPos && totalLoops>=exportLoopCount) { logD("start fading out..."); isFadingOut=true; if (fadeOutSamples==0) break; } } } if (sf_writef_float(sf,outBufFinal,total)!=(int)total) { logE("error: failed to write entire buffer!"); break; } } delete[] outBufFinal; for (int i=0; isetRun(true)) { logE("error while activating audio!"); } } logI("done!"); exporting=false; break; } case DIV_EXPORT_MODE_MANY_SYS: { SNDFILE* sf[DIV_MAX_CHIPS]; SF_INFO si[DIV_MAX_CHIPS]; String fname[DIV_MAX_CHIPS]; SFWrapper sfWrap[DIV_MAX_CHIPS]; for (int i=0; igetOutputCount(); if (exportFormat==DIV_EXPORT_FORMAT_S16) { si[i].format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; } else { si[i].format=SF_FORMAT_WAV|SF_FORMAT_FLOAT; } } for (int i=0; igetOutputCount()]; } // take control of audio output deinitAudioBackend(); playSub(false); logI("rendering to files..."); while (playing) { size_t total=0; nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); if (totalProcessed>EXPORT_BUFSIZE) { logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); totalProcessed=EXPORT_BUFSIZE; } for (int j=0; j<(int)totalProcessed; j++) { total++; if (isFadingOut) { double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); for (int i=0; i=fadeOutSamples) { playing=false; break; } } else { for (int i=0; i-1 && j>=lastLoopPos && totalLoops>=exportLoopCount) { logD("start fading out..."); isFadingOut=true; if (fadeOutSamples==0) break; } } } for (int i=0; isetRun(true)) { logE("error while activating audio!"); } } logI("done!"); exporting=false; break; } case DIV_EXPORT_MODE_MANY_CHAN: { // take control of audio output deinitAudioBackend(); curExportChan=0; float* outBuf[DIV_MAX_OUTPUTS]; float* outBufFinal; for (int i=0; imuteChannel(dispatchChanOfChan[j],isMuted[j]); } } curOrder=0; prevOrder=0; curFadeOutSample=0; lastLoopPos=-1; totalLoops=0; isFadingOut=false; remainingLoops=-1; playSub(false); while (playing) { size_t total=0; nextBuf(NULL,outBuf,0,exportOutputs,EXPORT_BUFSIZE); if (totalProcessed>EXPORT_BUFSIZE) { logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); totalProcessed=EXPORT_BUFSIZE; } int fi=0; for (int j=0; j<(int)totalProcessed; j++) { total++; if (isFadingOut) { double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples)); for (int k=0; k=fadeOutSamples) { playing=false; break; } } else { for (int k=0; k-1 && j>=lastLoopPos && totalLoops>=exportLoopCount) { logD("start fading out..."); isFadingOut=true; if (fadeOutSamples==0) break; } } } if (sf_writef_float(sf,outBufFinal,total)!=(int)total) { logE("error: failed to write entire buffer!"); break; } } curExportChan++; if (sfWrap.doClose()!=0) { logE("could not close audio file!"); } if (getChannelType(i)==5) { i++; while (true) { if (i>=chans) break; if (getChannelType(i)!=5) break; i++; } i--; } if (stopExport) break; } delete[] outBufFinal; for (int i=0; imuteChannel(dispatchChanOfChan[i],false); } } if (initAudioBackend()) { for (int i=0; isetRun(true)) { logE("error while activating audio!"); } } logI("done!"); exporting=false; curExportChan=0; break; } } stopExport=false; } #else void DivEngine::runExportThread() { } #endif bool DivEngine::shallSwitchCores() { return true; } bool DivEngine::saveAudio(const char* path, DivAudioExportOptions options) { #ifndef HAVE_SNDFILE logE("Furnace was not compiled with libsndfile. cannot export!"); return false; #else exportPath=path; exportMode=options.mode; exportFormat=options.format; exportFadeOut=options.fadeOut; memcpy(exportChannelMask,options.channelMask,DIV_MAX_CHANS*sizeof(bool)); if (exportMode!=DIV_EXPORT_MODE_ONE) { // remove extension String lowerCase=exportPath; for (char& i: lowerCase) { if (i>='A' && i<='Z') i+='a'-'A'; } size_t extPos=lowerCase.rfind(".wav"); if (extPos!=String::npos) { exportPath=exportPath.substr(0,extPos); } } exporting=true; stopExport=false; stop(); repeatPattern=false; setOrder(0); remainingLoops=-1; got.rate=options.sampleRate; if (shallSwitchCores()) { bool isMutedBefore[DIV_MAX_CHANS]; memcpy(isMutedBefore,isMuted,DIV_MAX_CHANS*sizeof(bool)); quitDispatch(); initDispatch(true); renderSamplesP(); for (int i=0; iDIV_MAX_OUTPUTS) exportOutputs=DIV_MAX_OUTPUTS; exportLoopCount=options.loops+1; exportThread=new std::thread(_runExportThread,this); return true; #endif } void DivEngine::waitAudioFile() { if (exportThread!=NULL) { exportThread->join(); } } bool DivEngine::haltAudioFile() { stopExport=true; stop(); waitAudioFile(); finishAudioFile(); return true; } void DivEngine::finishAudioFile() { if (shallSwitchCores()) { bool isMutedBefore[DIV_MAX_CHANS]; memcpy(isMutedBefore,isMuted,DIV_MAX_CHANS*sizeof(bool)); quitDispatch(); initDispatch(false); renderSamplesP(); for (int i=0; i