/** * Furnace Tracker - multi-system chiptune tracker * Copyright (C) 2021-2026 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 "filePlayer.h" #include "filter.h" #include "../ta-log.h" #include #include #define DIV_FPCACHE_BLOCK_SHIFT 15 #define DIV_FPCACHE_BLOCK_SIZE (1<>DIV_FPCACHE_BLOCK_SHIFT; ssize_t lastBlock=firstBlock+DIV_FPCACHE_BLOCKS_FROM_FILL; if (firstBlock<0) firstBlock=0; if (firstBlock>=(ssize_t)numBlocks) firstBlock=numBlocks-1; if (lastBlock<0) lastBlock=0; if (lastBlock>=(ssize_t)numBlocks) lastBlock=numBlocks-1; // don't read if we're after end of file if (firstBlock>lastBlock) return; bool needToFill=false; for (ssize_t i=firstBlock; i<=lastBlock; i++) { if (i<0 || i>=(ssize_t)numBlocks) continue; if (!blocks[i]) { needToFill=true; firstBlock=i; break; } } if (!needToFill) return; // check whether we need to seek sf_count_t curSeek=sf_seek(sf,0,SEEK_CUR); if (curSeek==-1) { // I/O error fileError=true; return; } if ((curSeek&DIV_FPCACHE_BLOCK_MASK)!=0 || (curSeek>>DIV_FPCACHE_BLOCK_SHIFT)!=firstBlock) { // we need to seek logV("- seeking"); // we seek to a previous position in order to compensate for possible decoding differences when seeking // (usually in lossy codecs) sf_count_t seekWhere=firstBlock<>=DIV_FPCACHE_BLOCK_SHIFT; if (pos<0) pos=0; if (pos>=(ssize_t)numBlocks) pos=numBlocks-1; // collect garbage // start with blocks before the given position // then try with blocks after given position // prioritize far away ones // do not destroy priority blocks for (ssize_t i=0; ipos+DIV_FPCACHE_BLOCKS_FROM_FILL; i--) { if (!blocks[i]) continue; if (priorityBlock[i]) continue; logV("erasing block %d",(int)i); float* block=blocks[i]; blocks[i]=NULL; delete[] block; memUsage-=DIV_FPCACHE_BLOCK_SIZE*si.channels*sizeof(float); if (memUsage lock(cacheThreadLock); while (!quitThread) { ssize_t wantBlockC=wantBlock; if (wantBlockC!=DIV_NO_BLOCK) { wantBlock=DIV_NO_BLOCK; logV("thread fill %" PRIu64,wantBlockC); fillBlocksNear(wantBlockC); collectGarbage(wantBlockC); } cacheCV.wait(lock); } threadHasQuit=true; logV("DivFilePlayer: cache thread over."); } float DivFilePlayer::getSampleAt(ssize_t pos, int ch) { if (blocks==NULL) return 0.0f; ssize_t blockIndex=pos>>DIV_FPCACHE_BLOCK_SHIFT; if (blockIndex<0 || blockIndex>=(ssize_t)numBlocks) return 0.0f; float* block=blocks[blockIndex]; size_t posInBlock=(pos&DIV_FPCACHE_BLOCK_MASK)*si.channels+ch; if (block==NULL) return 0.0f; return block[posInBlock]; } void DivFilePlayer::mix(float** buf, int chans, unsigned int size) { // fill with zero if we don't have a file if (sf==NULL) { for (int i=0; i1.0f) actualVolume=1.0f; if (wantBlock!=DIV_NO_BLOCK) { cacheCV.notify_one(); } for (unsigned int i=0; i>DIV_FPCACHE_BLOCK_SHIFT; if (blockIndex!=lastWantBlock) { wantBlock=playPos; cacheCV.notify_one(); lastWantBlock=blockIndex; } if (playing) { // sinc interpolation float x[8]; unsigned int n=(8192*rateAccum)/outRate; n&=8191; float* t1=&sincTable[(8191-n)<<2]; float* t2=&sincTable[n<<2]; if (si.channels==1) { // mono optimization for (int k=0; k<8; k++) { x[k]=getSampleAt(playPos+k-3,0); } float s=( x[0]*t2[3]+ x[1]*t2[2]+ x[2]*t2[1]+ x[3]*t2[0]+ x[4]*t1[0]+ x[5]*t1[1]+ x[6]*t1[2]+ x[7]*t1[3] )*actualVolume; for (int j=0; j=si.channels) { buf[j][i]=0.0f; continue; } for (int k=0; k<8; k++) { x[k]=getSampleAt(playPos+k-3,j); } buf[j][i]=( x[0]*t2[3]+ x[1]*t2[2]+ x[2]*t2[1]+ x[3]*t2[0]+ x[4]*t1[0]+ x[5]*t1[1]+ x[6]*t1[2]+ x[7]*t1[3] )*actualVolume; } // advance rateAccum+=si.samplerate; while (rateAccum>=outRate) { rateAccum-=outRate; playPos++; /*if (playPos>=(ssize_t)si.frames) { playPos=0; }*/ } } else { for (int j=0; j>DIV_FPCACHE_BLOCK_SHIFT; if (which<0 || which>=(ssize_t)numBlocks) return false; return (blocks[which]!=NULL); } bool DivFilePlayer::setBlockPriority(ssize_t pos, bool priority) { if (priorityBlock==NULL) return false; ssize_t which=pos>>DIV_FPCACHE_BLOCK_SHIFT; if (which<0 || which>=(ssize_t)numBlocks) return false; priorityBlock[which]=priority; return priority; } bool DivFilePlayer::isLoaded() { return (sf!=NULL); } bool DivFilePlayer::isPlaying() { return playing; } void DivFilePlayer::play(unsigned int offset) { if (offset!=UINT_MAX) { pendingPlayOffset=offset; logV("DivFilePlayer: playing (offset: %u)",offset); } else { playing=true; logV("DivFilePlayer: playing"); } } void DivFilePlayer::stop(unsigned int offset) { if (offset!=UINT_MAX) { pendingStopOffset=offset; logV("DivFilePlayer: stopping (offset: %u)",offset); } else { playing=false; logV("DivFilePlayer: stopping"); } } bool DivFilePlayer::closeFile() { if (sf==NULL) return false; logD("DivFilePlayer: closing file."); if (cacheThread) { quitThread=true; while (!threadHasQuit) { cacheCV.notify_one(); std::this_thread::yield(); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } // this join is guaranteed to work cacheThread->join(); delete cacheThread; cacheThread=NULL; } sfw.doClose(); sf=NULL; playing=false; quitThread=false; threadHasQuit=false; for (size_t i=0; i>DIV_FPCACHE_BLOCK_SHIFT; blocks=new float*[numBlocks]; priorityBlock=new bool[numBlocks]; memset(blocks,0,numBlocks*sizeof(void*)); memset(priorityBlock,0,numBlocks*sizeof(bool)); // mark the first blocks as important for (size_t i=0; i=numBlocks) break; priorityBlock[i]=true; } playPos=0; lastWantBlock=DIV_NO_BLOCK; rateAccum=0; fileError=false; // read the entire file if not seekable if (!si.seekable) { logV("file not seekable - reading..."); for (size_t i=0; i