/** * Furnace Tracker - multi-system chiptune tracker * Copyright (C) 2021-2023 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. */ #define _USE_MATH_DEFINES #include "dispatch.h" #include "song.h" #include "engine.h" #include "instrument.h" #include "safeReader.h" #include "../ta-log.h" #include "../fileutils.h" #ifdef HAVE_SDL2 #include "../audio/sdlAudio.h" #endif #include #ifdef HAVE_JACK #include "../audio/jack.h" #endif #ifdef HAVE_PA #include "../audio/pa.h" #endif #include #include #include void process(void* u, float** in, float** out, int inChans, int outChans, unsigned int size) { ((DivEngine*)u)->nextBuf(in,out,inChans,outChans,size); } const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNull) { switch (effect) { case 0x00: return "00xy: Arpeggio"; case 0x01: return "01xx: Pitch slide up"; case 0x02: return "02xx: Pitch slide down"; case 0x03: return "03xx: Portamento"; case 0x04: return "04xy: Vibrato (x: speed; y: depth)"; case 0x05: return "05xy: Volume slide + vibrato (compatibility only!)"; case 0x06: return "06xy: Volume slide + portamento (compatibility only!)"; case 0x07: return "07xy: Tremolo (x: speed; y: depth)"; case 0x08: return "08xy: Set panning (x: left; y: right)"; case 0x09: return "09xx: Set groove pattern (speed 1 if no grooves exist)"; case 0x0a: return "0Axy: Volume slide (0y: down; x0: up)"; case 0x0b: return "0Bxx: Jump to pattern"; case 0x0c: return "0Cxx: Retrigger"; case 0x0d: return "0Dxx: Jump to next pattern"; case 0x0f: return "0Fxx: Set speed (speed 2 if no grooves exist)"; case 0x80: return "80xx: Set panning (00: left; 80: center; FF: right)"; case 0x81: return "81xx: Set panning (left channel)"; case 0x82: return "82xx: Set panning (right channel)"; case 0x88: return "88xx: Set panning (rear channels; x: left; y: right)"; break; case 0x89: return "89xx: Set panning (rear left channel)"; break; case 0x8a: return "8Axx: Set panning (rear right channel)"; break; case 0xc0: case 0xc1: case 0xc2: case 0xc3: return "Cxxx: Set tick rate (hz)"; case 0xe0: return "E0xx: Set arp speed"; case 0xe1: return "E1xy: Note slide up (x: speed; y: semitones)"; case 0xe2: return "E2xy: Note slide down (x: speed; y: semitones)"; case 0xe3: return "E3xx: Set vibrato shape (0: up/down; 1: up only; 2: down only)"; case 0xe4: return "E4xx: Set vibrato range"; case 0xe5: return "E5xx: Set pitch (80: center)"; case 0xea: return "EAxx: Legato"; case 0xeb: return "EBxx: Set LEGACY sample mode bank"; case 0xec: return "ECxx: Note cut"; case 0xed: return "EDxx: Note delay"; case 0xee: return "EExx: Send external command"; case 0xf0: return "F0xx: Set tick rate (bpm)"; case 0xf1: return "F1xx: Single tick note slide up"; case 0xf2: return "F2xx: Single tick note slide down"; case 0xf3: return "F3xx: Fine volume slide up"; case 0xf4: return "F4xx: Fine volume slide down"; case 0xf5: return "F5xx: Disable macro (see manual)"; case 0xf6: return "F6xx: Enable macro (see manual)"; case 0xf8: return "F8xx: Single tick volume slide up"; case 0xf9: return "F9xx: Single tick volume slide down"; case 0xfa: return "FAxx: Fast volume slide (0y: down; x0: up)"; case 0xff: return "FFxx: Stop song"; default: if ((effect&0xf0)==0x90) { return "9xxx: Set sample offset*256"; } else if (chan>=0 && chaneffectHandlers.find(effect); if (iter!=sysDef->effectHandlers.end()) { return iter->second.description; } iter=sysDef->postEffectHandlers.find(effect); if (iter!=sysDef->postEffectHandlers.end()) { return iter->second.description; } } break; } return notNull?"Invalid effect":NULL; } void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) { loopOrder=0; loopRow=0; loopEnd=-1; int nextOrder=-1; int nextRow=0; int effectVal=0; int lastSuspectedLoopEnd=-1; DivPattern* pat[DIV_MAX_CHANS]; unsigned char wsWalked[8192]; memset(wsWalked,0,8192); for (int i=0; iordersLen; i++) { for (int j=0; jord[j][i],false); } if (i>lastSuspectedLoopEnd) { lastSuspectedLoopEnd=i; } for (int j=nextRow; jpatLen; j++) { nextRow=0; bool changingOrder=false; bool jumpingOrder=false; if (wsWalked[((i<<5)+(j>>3))&8191]&(1<<(j&7))) { loopOrder=i; loopRow=j; loopEnd=lastSuspectedLoopEnd; return; } for (int k=0; kdata[j][5+(l<<1)]; if (effectVal<0) effectVal=0; if (pat[k]->data[j][4+(l<<1)]==0x0d) { if (song.jumpTreatment==2) { if ((iordersLen-1 || !song.ignoreJumpAtEnd)) { nextOrder=i+1; nextRow=effectVal; jumpingOrder=true; } } else if (song.jumpTreatment==1) { if (nextOrder==-1 && (iordersLen-1 || !song.ignoreJumpAtEnd)) { nextOrder=i+1; nextRow=effectVal; jumpingOrder=true; } } else { if ((iordersLen-1 || !song.ignoreJumpAtEnd)) { if (!changingOrder) { nextOrder=i+1; } jumpingOrder=true; nextRow=effectVal; } } } else if (pat[k]->data[j][4+(l<<1)]==0x0b) { if (nextOrder==-1 || song.jumpTreatment==0) { nextOrder=effectVal; if (song.jumpTreatment==1 || song.jumpTreatment==2 || !jumpingOrder) { nextRow=0; } changingOrder=true; } } } } wsWalked[((i<<5)+(j>>3))&8191]|=1<<(j&7); if (nextOrder!=-1) { i=nextOrder-1; nextOrder=-1; break; } } } } #define EXPORT_BUFSIZE 2048 double DivEngine::benchmarkPlayback() { float* outBuf[2]; outBuf[0]=new float[EXPORT_BUFSIZE]; outBuf[1]=new float[EXPORT_BUFSIZE]; curOrder=0; prevOrder=0; remainingLoops=1; playSub(false); std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now(); // benchmark while (playing) { nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); } std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now(); delete[] outBuf[0]; delete[] outBuf[1]; double t=(double)(std::chrono::duration_cast(timeEnd-timeStart).count())/1000000.0; printf("[RESULT] %fs\n",t); return t; } double DivEngine::benchmarkSeek() { double t[20]; curOrder=curSubSong->ordersLen-1; prevOrder=curSubSong->ordersLen-1; // benchmark for (int i=0; i<20; i++) { std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now(); playSub(false); std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now(); t[i]=(double)(std::chrono::duration_cast(timeEnd-timeStart).count())/1000000.0; printf("[#%d] %fs\n",i+1,t[i]); } double tMin=DBL_MAX; double tMax=0.0; double tAvg=0.0; for (int i=0; i<20; i++) { if (t[i]tMax) tMax=t[i]; tAvg+=t[i]; } tAvg/=20.0; printf("[RESULT] min %fs max %fs average %fs\n",tMin,tMax,tAvg); return tAvg; } void DivEngine::notifyInsChange(int ins) { BUSY_BEGIN; for (int i=0; inotifyInsChange(ins); } BUSY_END; } void DivEngine::notifyWaveChange(int wave) { BUSY_BEGIN; for (int i=0; inotifyWaveChange(wave); } BUSY_END; } int DivEngine::loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret) { ret=NULL; if (path.empty()) { return 0; } logI("loading ROM %s...",path); FILE* f=ps_fopen(path.c_str(),"rb"); if (f==NULL) { logE("error: %s",strerror(errno)); lastError=strerror(errno); return -1; } if (fseek(f,0,SEEK_END)<0) { logE("size error: %s",strerror(errno)); lastError=fmt::sprintf("on seek: %s",strerror(errno)); fclose(f); return -1; } ssize_t len=ftell(f); if (len==(SIZE_MAX>>1)) { logE("could not get file length: %s",strerror(errno)); lastError=fmt::sprintf("on pre tell: %s",strerror(errno)); fclose(f); return -1; } if (len<1) { if (len==0) { logE("that file is empty!"); lastError="file is empty"; } else { logE("tell error: %s",strerror(errno)); lastError=fmt::sprintf("on tell: %s",strerror(errno)); } fclose(f); return -1; } if (len!=expectedSize) { logE("ROM size mismatch, expected: %d bytes, was: %d bytes", expectedSize, len); lastError=fmt::sprintf("ROM size mismatch, expected: %d bytes, was: %d", expectedSize, len); return -1; } if (fseek(f,0,SEEK_SET)<0) { logE("size error: %s",strerror(errno)); lastError=fmt::sprintf("on get size: %s",strerror(errno)); fclose(f); return -1; } unsigned char* file=new unsigned char[len]; if (fread(file,1,(size_t)len,f)!=(size_t)len) { logE("read error: %s",strerror(errno)); lastError=fmt::sprintf("on read: %s",strerror(errno)); fclose(f); delete[] file; return -1; } fclose(f); ret=file; return 0; } unsigned int DivEngine::getSampleFormatMask() { unsigned int formatMask=1U<<16; // 16-bit is always on for (int i=0; isampleFormatMask; } return formatMask; } int DivEngine::loadSampleROMs() { if (yrw801ROM!=NULL) { delete[] yrw801ROM; yrw801ROM=NULL; } if (tg100ROM!=NULL) { delete[] tg100ROM; tg100ROM=NULL; } if (mu5ROM!=NULL) { delete[] mu5ROM; mu5ROM=NULL; } int error=0; error+=loadSampleROM(getConfString("yrw801Path",""), 0x200000, yrw801ROM); error+=loadSampleROM(getConfString("tg100Path",""), 0x200000, tg100ROM); error+=loadSampleROM(getConfString("mu5Path",""), 0x200000, mu5ROM); return error; } void DivEngine::renderSamplesP() { BUSY_BEGIN; renderSamples(); BUSY_END; } void DivEngine::renderSamples() { sPreview.sample=-1; sPreview.pos=0; sPreview.dir=false; logD("rendering samples..."); // step 0: make sample format mask unsigned int formatMask=1U<<16; // 16-bit is always on for (int i=0; isampleFormatMask; } // step 1: render samples for (int i=0; irender(formatMask); } // step 2: render samples to dispatch for (int i=0; irenderSamples(i); } } } String DivEngine::decodeSysDesc(String desc) { DivConfig newDesc; bool hasVal=false; bool negative=false; int val=0; int curStage=0; int sysID=0; float sysVol=0; float sysPan=0; int sysFlags=0; int curSys=0; desc+=' '; // ha for (char i: desc) { switch (i) { case ' ': if (hasVal) { if (negative) val=-val; switch (curStage) { case 0: sysID=val; curStage++; break; case 1: sysVol=(float)val/64.0f; curStage++; break; case 2: sysPan=(float)val/127.0f; curStage++; break; case 3: sysFlags=val; if (sysID!=0) { if (sysVol<-1.0f) sysVol=-1.0f; if (sysVol>1.0f) sysVol=1.0f; if (sysPan<-1.0f) sysPan=-1.0f; if (sysPan>1.0f) sysPan=1.0f; newDesc.set(fmt::sprintf("id%d",curSys),sysID); newDesc.set(fmt::sprintf("vol%d",curSys),sysVol); newDesc.set(fmt::sprintf("pan%d",curSys),sysPan); newDesc.set(fmt::sprintf("fr%d",curSys),0.0f); DivConfig newFlagsC; newFlagsC.clear(); convertOldFlags((unsigned int)sysFlags,newFlagsC,systemFromFileFur(sysID)); newDesc.set(fmt::sprintf("flags%d",curSys),newFlagsC.toBase64()); curSys++; } curStage=0; break; } hasVal=false; negative=false; val=0; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': val=(val*10)+(i-'0'); hasVal=true; break; case '-': if (!hasVal) negative=true; break; } } return newDesc.toBase64(); } void DivEngine::initSongWithDesc(const char* description, bool inBase64, bool oldVol) { int chanCount=0; DivConfig c; if (inBase64) { c.loadFromBase64(description); } else { c.loadFromMemory(description); } int index=0; for (; index=DIV_MAX_CHANS) { song.system[index]=DIV_SYSTEM_NULL; break; } song.systemVol[index]=c.getFloat(fmt::sprintf("vol%d",index),1.0f); song.systemPan[index]=c.getFloat(fmt::sprintf("pan%d",index),0.0f); song.systemPanFR[index]=c.getFloat(fmt::sprintf("fr%d",index),0.0f); song.systemFlags[index].clear(); if (oldVol) { song.systemVol[index]/=64.0f; song.systemPan[index]/=127.0f; } String flags=c.getString(fmt::sprintf("flags%d",index),""); song.systemFlags[index].loadFromBase64(flags.c_str()); } song.systemLen=index; // extra attributes song.subsong[0]->hz=c.getDouble("tickRate",60.0); } void DivEngine::createNew(const char* description, String sysName, bool inBase64) { quitDispatch(); BUSY_BEGIN; saveLock.lock(); song.unload(); song=DivSong(); changeSong(0); if (description!=NULL) { initSongWithDesc(description,inBase64); } if (sysName=="") { song.systemName=getSongSystemLegacyName(song,!getConfInt("noMultiSystem",0)); } else { song.systemName=sysName; } recalcChans(); saveLock.unlock(); BUSY_END; initDispatch(); BUSY_BEGIN; renderSamples(); reset(); BUSY_END; } void DivEngine::createNewFromDefaults() { quitDispatch(); BUSY_BEGIN; saveLock.lock(); song.unload(); song=DivSong(); changeSong(0); String preset=getConfString("initialSys2",""); bool oldVol=getConfInt("configVersion",DIV_ENGINE_VERSION)<135; if (preset.empty()) { // try loading old preset logD("trying to load old preset"); preset=decodeSysDesc(getConfString("initialSys","")); oldVol=false; } logD("preset size %ld",preset.size()); if (preset.size()>0 && (preset.size()&3)==0) { initSongWithDesc(preset.c_str(),true,oldVol); } String sysName=getConfString("initialSysName",""); if (sysName=="") { song.systemName=getSongSystemLegacyName(song,!getConfInt("noMultiSystem",0)); } else { song.systemName=sysName; } recalcChans(); saveLock.unlock(); BUSY_END; initDispatch(); BUSY_BEGIN; renderSamples(); reset(); BUSY_END; } void DivEngine::swapChannels(int src, int dest) { logV("swapping channel %d with %d",src,dest); if (src==dest) { logV("not swapping channels because it's the same channel!",src,dest); return; } for (int i=0; iord[dest][i]^=curOrders->ord[src][i]; curOrders->ord[src][i]^=curOrders->ord[dest][i]; curOrders->ord[dest][i]^=curOrders->ord[src][i]; DivPattern* prev=curPat[src].data[i]; curPat[src].data[i]=curPat[dest].data[i]; curPat[dest].data[i]=prev; } curPat[src].effectCols^=curPat[dest].effectCols; curPat[dest].effectCols^=curPat[src].effectCols; curPat[src].effectCols^=curPat[dest].effectCols; String prevChanName=curSubSong->chanName[src]; String prevChanShortName=curSubSong->chanShortName[src]; bool prevChanShow=curSubSong->chanShow[src]; unsigned char prevChanCollapse=curSubSong->chanCollapse[src]; curSubSong->chanName[src]=curSubSong->chanName[dest]; curSubSong->chanShortName[src]=curSubSong->chanShortName[dest]; curSubSong->chanShow[src]=curSubSong->chanShow[dest]; curSubSong->chanCollapse[src]=curSubSong->chanCollapse[dest]; curSubSong->chanName[dest]=prevChanName; curSubSong->chanShortName[dest]=prevChanShortName; curSubSong->chanShow[dest]=prevChanShow; curSubSong->chanCollapse[dest]=prevChanCollapse; } void DivEngine::stompChannel(int ch) { logV("stomping channel %d",ch); for (int i=0; iord[ch][i]=0; } curPat[ch].wipePatterns(); curPat[ch].effectCols=1; curSubSong->chanName[ch]=""; curSubSong->chanShortName[ch]=""; curSubSong->chanShow[ch]=true; curSubSong->chanCollapse[ch]=false; } void DivEngine::changeSong(size_t songIndex) { if (songIndex>=song.subsong.size()) return; curSubSong=song.subsong[songIndex]; curPat=song.subsong[songIndex]->pat; curOrders=&song.subsong[songIndex]->orders; curSubSongIndex=songIndex; curOrder=0; curRow=0; prevOrder=0; prevRow=0; } void DivEngine::moveAsset(std::vector& dir, int before, int after) { if (before<0 || after<0) return; for (DivAssetDir& i: dir) { for (size_t j=0; j& dir, int entry) { if (entry<0) return; for (DivAssetDir& i: dir) { for (size_t j=0; jentry) { i.entries[j]--; } } } } void DivEngine::checkAssetDir(std::vector& dir, size_t entries) { bool* inAssetDir=new bool[entries]; memset(inAssetDir,0,entries*sizeof(bool)); for (DivAssetDir& i: dir) { for (size_t j=0; j=(int)entries) { i.entries.erase(i.entries.begin()+j); j--; continue; } // erase duplicate entry if (inAssetDir[i.entries[j]]) { i.entries.erase(i.entries.begin()+j); j--; continue; } // mark entry as present inAssetDir[i.entries[j]]=true; } } // get unsorted directory DivAssetDir* unsortedDir=NULL; for (DivAssetDir& i: dir) { if (i.name.empty()) { unsortedDir=&i; break; } } // add missing items to unsorted directory for (size_t i=0; ientries.push_back(i); } } delete[] inAssetDir; } void DivEngine::swapChannelsP(int src, int dest) { if (src<0 || src>=chans) return; if (dest<0 || dest>=chans) return; BUSY_BEGIN; saveLock.lock(); swapChannels(src,dest); saveLock.unlock(); BUSY_END; } void DivEngine::changeSongP(size_t index) { if (index>=song.subsong.size()) return; if (index==curSubSongIndex) return; stop(); BUSY_BEGIN; saveLock.lock(); changeSong(index); saveLock.unlock(); BUSY_END; } int DivEngine::addSubSong() { if (song.subsong.size()>=127) return -1; BUSY_BEGIN; saveLock.lock(); song.subsong.push_back(new DivSubSong); saveLock.unlock(); BUSY_END; return song.subsong.size()-1; } int DivEngine::duplicateSubSong(int index) { if (song.subsong.size()>=127) return -1; BUSY_BEGIN; saveLock.lock(); DivSubSong* theCopy=new DivSubSong; DivSubSong* theOrig=song.subsong[index]; theCopy->name=theOrig->name; theCopy->notes=theOrig->notes; theCopy->hilightA=theOrig->hilightA; theCopy->hilightB=theOrig->hilightB; theCopy->timeBase=theOrig->timeBase; theCopy->arpLen=theOrig->arpLen; theCopy->speeds=theOrig->speeds; theCopy->virtualTempoN=theOrig->virtualTempoN; theCopy->virtualTempoD=theOrig->virtualTempoD; theCopy->hz=theOrig->hz; theCopy->patLen=theOrig->patLen; theCopy->ordersLen=theOrig->ordersLen; theCopy->orders=theOrig->orders; memcpy(theCopy->chanShow,theOrig->chanShow,DIV_MAX_CHANS*sizeof(bool)); memcpy(theCopy->chanCollapse,theOrig->chanCollapse,DIV_MAX_CHANS); for (int i=0; ichanName[i]=theOrig->chanName[i]; theCopy->chanShortName[i]=theOrig->chanShortName[i]; theCopy->pat[i].effectCols=theOrig->pat[i].effectCols; for (int j=0; jpat[i].data[j]==NULL) continue; DivPattern* origPat=theOrig->pat[i].getPattern(j,false); DivPattern* copyPat=theCopy->pat[i].getPattern(j,true); origPat->copyOn(copyPat); } } song.subsong.push_back(theCopy); saveLock.unlock(); BUSY_END; return song.subsong.size()-1; } bool DivEngine::removeSubSong(int index) { if (song.subsong.size()<=1) return false; stop(); BUSY_BEGIN; saveLock.lock(); song.subsong[index]->clearData(); delete song.subsong[index]; song.subsong.erase(song.subsong.begin()+index); changeSong(0); saveLock.unlock(); BUSY_END; return true; } void DivEngine::moveSubSongUp(size_t index) { if (index<1 || index>=song.subsong.size()) return; BUSY_BEGIN; saveLock.lock(); if (index==curSubSongIndex) { curSubSongIndex--; } else if (index-1==curSubSongIndex) { curSubSongIndex++; } DivSubSong* prev=song.subsong[index-1]; song.subsong[index-1]=song.subsong[index]; song.subsong[index]=prev; saveLock.unlock(); BUSY_END; } void DivEngine::moveSubSongDown(size_t index) { if (index>=song.subsong.size()-1) return; BUSY_BEGIN; saveLock.lock(); if (index==curSubSongIndex) { curSubSongIndex++; } else if (index+1==curSubSongIndex) { curSubSongIndex--; } DivSubSong* prev=song.subsong[index+1]; song.subsong[index+1]=song.subsong[index]; song.subsong[index]=prev; saveLock.unlock(); BUSY_END; } void DivEngine::clearSubSongs() { BUSY_BEGIN; saveLock.lock(); song.clearSongData(); changeSong(0); curOrder=0; prevOrder=0; saveLock.unlock(); BUSY_END; } void DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { int chanCount=chans; quitDispatch(); BUSY_BEGIN; saveLock.lock(); if (!preserveOrder) { int firstChan=0; int chanMovement=getChannelCount(which)-getChannelCount(song.system[index]); while (dispatchOfChan[firstChan]!=index) firstChan++; int lastChan=firstChan+getChannelCount(song.system[index]); if (chanMovement!=0) { if (chanMovement>0) { // add channels for (int i=chanCount+chanMovement-1; i>=lastChan+chanMovement; i--) { swapChannels(i,i-chanMovement); } for (int i=lastChan; iDIV_MAX_CHIPS) { lastError=fmt::sprintf("max number of systems is %d",DIV_MAX_CHIPS); return false; } if (chans+getChannelCount(which)>DIV_MAX_CHANS) { lastError=fmt::sprintf("max number of total channels is %d",DIV_MAX_CHANS); return false; } quitDispatch(); BUSY_BEGIN; saveLock.lock(); song.system[song.systemLen]=which; song.systemVol[song.systemLen]=1.0; song.systemPan[song.systemLen]=0; song.systemPanFR[song.systemLen]=0; song.systemFlags[song.systemLen++].clear(); recalcChans(); saveLock.unlock(); BUSY_END; initDispatch(); BUSY_BEGIN; saveLock.lock(); if (song.patchbayAuto) { autoPatchbay(); } else { int i=song.systemLen-1; if (disCont[i].dispatch!=NULL) { unsigned int outs=disCont[i].dispatch->getOutputCount(); if (outs>16) outs=16; if (outs<2) { song.patchbay.reserve(DIV_MAX_OUTPUTS); for (unsigned int j=0; j=song.systemLen) { lastError="invalid index"; return false; } int chanCount=chans; quitDispatch(); BUSY_BEGIN; saveLock.lock(); if (!preserveOrder) { int firstChan=0; while (dispatchOfChan[firstChan]!=index) firstChan++; for (int i=0; i>20)&0xfff)==(unsigned int)index) { song.patchbay.erase(song.patchbay.begin()+i); i--; } } song.system[index]=DIV_SYSTEM_NULL; song.systemLen--; for (int i=index; i=song.systemLen) { lastError="invalid source index"; return false; } if (dest<0 || dest>=song.systemLen) { lastError="invalid destination index"; return false; } //int chanCount=chans; quitDispatch(); BUSY_BEGIN; saveLock.lock(); if (!preserveOrder) { // move channels unsigned char unswappedChannels[DIV_MAX_CHANS]; unsigned char swappedChannels[DIV_MAX_CHANS]; std::vector> swapList; std::vector chanList; int tchans=0; for (int i=0; i& i: swapList) { for (int& j: i) { swappedChannels[index++]=j; } } // swap channels logV("swap list:"); for (int i=0; i %d",unswappedChannels[i],swappedChannels[i]); } for (size_t i=0; iorders; DivPattern* prevPat[DIV_MAX_CHANS][DIV_MAX_PATTERNS]; unsigned char prevEffectCols[DIV_MAX_CHANS]; String prevChanName[DIV_MAX_CHANS]; String prevChanShortName[DIV_MAX_CHANS]; bool prevChanShow[DIV_MAX_CHANS]; unsigned char prevChanCollapse[DIV_MAX_CHANS]; for (int j=0; jpat[j].data[k]; } prevEffectCols[j]=song.subsong[i]->pat[j].effectCols; prevChanName[j]=song.subsong[i]->chanName[j]; prevChanShortName[j]=song.subsong[i]->chanShortName[j]; prevChanShow[j]=song.subsong[i]->chanShow[j]; prevChanCollapse[j]=song.subsong[i]->chanCollapse[j]; } for (int j=0; jorders.ord[j][k]=prevOrders.ord[swappedChannels[j]][k]; song.subsong[i]->pat[j].data[k]=prevPat[swappedChannels[j]][k]; } song.subsong[i]->pat[j].effectCols=prevEffectCols[swappedChannels[j]]; song.subsong[i]->chanName[j]=prevChanName[swappedChannels[j]]; song.subsong[i]->chanShortName[j]=prevChanShortName[swappedChannels[j]]; song.subsong[i]->chanShow[j]=prevChanShow[swappedChannels[j]]; song.subsong[i]->chanCollapse[j]=prevChanCollapse[swappedChannels[j]]; } } } DivSystem srcSystem=song.system[src]; float srcVol=song.systemVol[src]; float srcPan=song.systemPan[src]; float srcPanFR=song.systemPanFR[src]; song.system[src]=song.system[dest]; song.system[dest]=srcSystem; song.systemVol[src]=song.systemVol[dest]; song.systemVol[dest]=srcVol; song.systemPan[src]=song.systemPan[dest]; song.systemPan[dest]=srcPan; song.systemPanFR[src]=song.systemPanFR[dest]; song.systemPanFR[dest]=srcPanFR; // I am kinda scared to use std::swap DivConfig oldFlags=song.systemFlags[src]; song.systemFlags[src]=song.systemFlags[dest]; song.systemFlags[dest]=oldFlags; // patchbay for (unsigned int& i: song.patchbay) { if (((i>>20)&0xfff)==(unsigned int)src) { i=(i&(~0xfff00000))|((unsigned int)dest<<20); } else if (((i>>20)&0xfff)==(unsigned int)dest) { i=(i&(~0xfff00000))|((unsigned int)src<<20); } } recalcChans(); saveLock.unlock(); BUSY_END; initDispatch(); BUSY_BEGIN; renderSamples(); reset(); BUSY_END; return true; } void DivEngine::poke(int sys, unsigned int addr, unsigned short val) { if (sys<0 || sys>=song.systemLen) return; BUSY_BEGIN; disCont[sys].dispatch->poke(addr,val); BUSY_END; } void DivEngine::poke(int sys, std::vector& wlist) { if (sys<0 || sys>=song.systemLen) return; BUSY_BEGIN; disCont[sys].dispatch->poke(wlist); BUSY_END; } String DivEngine::getLastError() { return lastError; } String DivEngine::getWarnings() { return warnings; } String DivEngine::getPlaybackDebugInfo() { return fmt::sprintf( "curOrder: %d\n" "prevOrder: %d\n" "curRow: %d\n" "prevRow: %d\n" "ticks: %d\n" "subticks: %d\n" "totalLoops: %d\n" "lastLoopPos: %d\n" "nextSpeed: %d\n" "divider: %f\n" "cycles: %d\n" "clockDrift: %f\n" "midiClockCycles: %d\n" "midiClockDrift: %f\n" "midiTimeCycles: %d\n" "midiTimeDrift: %f\n" "changeOrd: %d\n" "changePos: %d\n" "totalSeconds: %d\n" "totalTicks: %d\n" "totalTicksR: %d\n" "curMidiClock: %d\n" "curMidiTime: %d\n" "totalCmds: %d\n" "lastCmds: %d\n" "cmdsPerSecond: %d\n" "globalPitch: %d\n" "extValue: %d\n" "tempoAccum: %d\n" "totalProcessed: %d\n" "bufferPos: %d\n", curOrder,prevOrder,curRow,prevRow,ticks,subticks,totalLoops,lastLoopPos,nextSpeed,divider,cycles,clockDrift, midiClockCycles,midiClockDrift,midiTimeCycles,midiTimeDrift,changeOrd,changePos,totalSeconds,totalTicks, totalTicksR,curMidiClock,curMidiTime,totalCmds,lastCmds,cmdsPerSecond,globalPitch, (int)extValue,(int)tempoAccum,(int)totalProcessed,(int)bufferPos ); } DivInstrument* DivEngine::getIns(int index, DivInstrumentType fallbackType) { if (index==-2 && tempIns!=NULL) { return tempIns; } if (index<0 || index>=song.insLen) { switch (fallbackType) { case DIV_INS_OPLL: return &song.nullInsOPLL; break; case DIV_INS_OPL: return &song.nullInsOPL; break; case DIV_INS_OPL_DRUMS: return &song.nullInsOPLDrums; break; default: break; } return &song.nullIns; } return song.ins[index]; } DivWavetable* DivEngine::getWave(int index) { if (index<0 || index>=song.waveLen) { if (song.waveLen>0) { return song.wave[0]; } else { return &song.nullWave; } } return song.wave[index]; } DivSample* DivEngine::getSample(int index) { if (index<0 || index>=song.sampleLen) return &song.nullSample; return song.sample[index]; } DivDispatch* DivEngine::getDispatch(int index) { if (index<0 || index>=song.systemLen) return NULL; return disCont[index].dispatch; } void DivEngine::setLoops(int loops) { remainingLoops=loops; } DivChannelState* DivEngine::getChanState(int ch) { if (ch<0 || ch>=chans) return NULL; return &chan[ch]; } unsigned short DivEngine::getChanPan(int ch) { if (ch<0 || ch>=chans) return 0; return disCont[dispatchOfChan[ch]].dispatch->getPan(dispatchChanOfChan[ch]); } void* DivEngine::getDispatchChanState(int ch) { if (ch<0 || ch>=chans) return NULL; return disCont[dispatchOfChan[ch]].dispatch->getChanState(dispatchChanOfChan[ch]); } unsigned char* DivEngine::getRegisterPool(int sys, int& size, int& depth) { if (sys<0 || sys>=song.systemLen) return NULL; if (disCont[sys].dispatch==NULL) return NULL; size=disCont[sys].dispatch->getRegisterPoolSize(); depth=disCont[sys].dispatch->getRegisterPoolDepth(); return disCont[sys].dispatch->getRegisterPool(); } DivMacroInt* DivEngine::getMacroInt(int chan) { if (chan<0 || chan>=chans) return NULL; return disCont[dispatchOfChan[chan]].dispatch->getChanMacroInt(dispatchChanOfChan[chan]); } DivSamplePos DivEngine::getSamplePos(int chan) { if (chan<0 || chan>=chans) return DivSamplePos(); return disCont[dispatchOfChan[chan]].dispatch->getSamplePos(dispatchChanOfChan[chan]); } DivDispatchOscBuffer* DivEngine::getOscBuffer(int chan) { if (chan<0 || chan>=chans) return NULL; return disCont[dispatchOfChan[chan]].dispatch->getOscBuffer(dispatchChanOfChan[chan]); } void DivEngine::enableCommandStream(bool enable) { cmdStreamEnabled=enable; } void DivEngine::getCommandStream(std::vector& where) { BUSY_BEGIN; where.clear(); where.reserve(cmdStream.size()); for (DivCommand& i: cmdStream) { where.push_back(i); } cmdStream.clear(); BUSY_END; } void DivEngine::playSub(bool preserveDrift, int goalRow) { logV("playSub() called"); std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now(); for (int i=0; isetSkipRegisterWrites(false); reset(); if (preserveDrift && curOrder==0) { logV("preserveDrift && curOrder is true"); return; } bool oldRepeatPattern=repeatPattern; repeatPattern=false; int goal=curOrder; curOrder=0; curRow=0; prevOrder=0; prevRow=0; stepPlay=0; if (curSubSong!=NULL) curSubSong->arpLen=1; int prevDrift, prevMidiClockDrift, prevMidiTimeDrift; prevDrift=clockDrift; prevMidiClockDrift=midiClockDrift; prevMidiTimeDrift=midiTimeDrift; clockDrift=0; cycles=0; midiClockCycles=0; midiClockDrift=0; midiTimeCycles=0; midiTimeDrift=0; if (!preserveDrift) { ticks=1; tempoAccum=0; totalTicks=0; totalSeconds=0; totalTicksR=0; curMidiClock=0; curMidiTime=0; curMidiTimeCode=0; curMidiTimePiece=0; totalLoops=0; lastLoopPos=-1; } endOfSong=false; // whaaaaa? curSpeed=0; playing=true; skipping=true; memset(walked,0,8192); for (int i=0; isetSkipRegisterWrites(true); logV("goal: %d goalRow: %d",goal,goalRow); while (playing && curOrder1)) { if (nextTick(preserveDrift)) { skipping=false; return; } if (!preserveDrift) { runMidiClock(cycles); runMidiTime(cycles); } if (oldOrder!=curOrder) break; if (ticks-((tempoAccum+curSubSong->virtualTempoN)/MAX(1,curSubSong->virtualTempoD))<1 && curRow>=goalRow) break; } for (int i=0; isetSkipRegisterWrites(false); if (goal>0 || goalRow>0) { for (int i=0; iforceIns(); } for (int i=0; i0) { curMidiTime--; } if (curMidiClock>0) { curMidiClock--; } curMidiTimePiece=0; } if (!preserveDrift) { ticks=1; subticks=1; prevOrder=curOrder; prevRow=curRow; tempoAccum=0; } skipping=false; cmdStream.clear(); std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now(); logV("playSub() took %dµs",std::chrono::duration_cast(timeEnd-timeStart).count()); } /* int DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) { double base=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(note+3)/12.0); return period? round((clock/base)/divider): base*(divider/clock); }*/ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) { if (song.linearPitch==2) { // full linear return (note<<7); } double base=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(note+3)/12.0); return period? (clock/base)/divider: base*(divider/clock); } #define CONVERT_FNUM_BLOCK(bf,bits,note) \ double tuning=song.tuning; \ if (tuning<400.0) tuning=400.0; \ if (tuning>500.0) tuning=500.0; \ int boundaryBottom=tuning*pow(2.0,0.25)*(divider/clock); \ int boundaryTop=2.0*tuning*pow(2.0,0.25)*(divider/clock); \ while (boundaryTop>((1<>=1; \ boundaryBottom>>=1; \ } \ int block=(note)/12; \ if (block<0) block=0; \ if (block>7) block=7; \ bf>>=block; \ if (bf<0) bf=0; \ /* octave boundaries */ \ while (bf>0 && bf0) { \ bf<<=1; \ block--; \ } \ if (bf>boundaryTop) { \ while (block<7 && bf>boundaryTop) { \ bf>>=1; \ block++; \ } \ if (bf>((1<0) { CONVERT_FNUM_BLOCK(bf,blockBits,nbase>>7) } else { return bf; } } if (song.linearPitch==1) { // global pitch multiplier int whatTheFuck=(1024+(globalPitch<<6)-(globalPitch<0?globalPitch-6:0)); if (whatTheFuck<1) whatTheFuck=1; // avoids division by zero but please kill me if (song.pitchMacroIsLinear) { pitch+=pitch2; } pitch+=2048; if (pitch<0) pitch=0; if (pitch>4095) pitch=4095; int ret=period? ((base*(reversePitchTable[pitch]))/whatTheFuck): (((base*(pitchTable[pitch]))>>10)*whatTheFuck)/1024; if (!song.pitchMacroIsLinear) { ret+=period?(-pitch2):pitch2; } return ret; } return period? base-pitch-pitch2: base+((pitch*octave)>>1)+pitch2; } int DivEngine::calcArp(int note, int arp, int offset) { if (arp<0) { if (!(arp&0x40000000)) return (arp|0x40000000)+offset; } else { if (arp&0x40000000) return (arp&(~0x40000000))+offset; } return note+arp; } int DivEngine::convertPanSplitToLinear(unsigned int val, unsigned char bits, int range) { int panL=val>>bits; int panR=val&((1<range) val=range; int maxV=(1<maxV) panL=maxV; if (panR>maxV) panR=maxV; return (panL<midiOut!=NULL) { if (midiOutClock) { output->midiOut->send(TAMidiMessage(TA_MIDI_POSITION,(curMidiClock>>7)&0x7f,curMidiClock&0x7f)); } if (midiOutTime) { TAMidiMessage msg; msg.type=TA_MIDI_SYSEX; msg.sysExData.reset(new unsigned char[10],std::default_delete()); msg.sysExLen=10; unsigned char* msgData=msg.sysExData.get(); int actualTime=curMidiTime; int timeRate=midiOutTimeRate; int drop=0; if (timeRate<1 || timeRate>4) { if (curSubSong->hz>=47.98 && curSubSong->hz<=48.02) { timeRate=1; } else if (curSubSong->hz>=49.98 && curSubSong->hz<=50.02) { timeRate=2; } else if (curSubSong->hz>=59.9 && curSubSong->hz<=60.11) { timeRate=4; } else { timeRate=4; } } switch (timeRate) { case 1: // 24 msgData[5]=(actualTime/(60*60*24))%24; msgData[6]=(actualTime/(60*24))%60; msgData[7]=(actualTime/24)%60; msgData[8]=actualTime%24; break; case 2: // 25 msgData[5]=(actualTime/(60*60*25))%24; msgData[6]=(actualTime/(60*25))%60; msgData[7]=(actualTime/25)%60; msgData[8]=actualTime%25; break; case 3: // 29.97 (NTSC drop) // drop drop=((actualTime/(30*60))-(actualTime/(30*600)))*2; actualTime+=drop; msgData[5]=(actualTime/(60*60*30))%24; msgData[6]=(actualTime/(60*30))%60; msgData[7]=(actualTime/30)%60; msgData[8]=actualTime%30; break; case 4: // 30 (NTSC non-drop) default: msgData[5]=(actualTime/(60*60*30))%24; msgData[6]=(actualTime/(60*30))%60; msgData[7]=(actualTime/30)%60; msgData[8]=actualTime%30; break; } msgData[5]|=(timeRate-1)<<5; msgData[0]=0xf0; msgData[1]=0x7f; msgData[2]=0x7f; msgData[3]=0x01; msgData[4]=0x01; msgData[9]=0xf7; output->midiOut->send(msg); } output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_PLAY,0,0)); } BUSY_END; } void DivEngine::playToRow(int row) { BUSY_BEGIN_SOFT; sPreview.sample=-1; sPreview.wave=-1; sPreview.pos=0; sPreview.dir=false; freelance=false; playSub(false,row); for (int i=0; imidiOut!=NULL) { output->midiOut->send(TAMidiMessage(TA_MIDI_CONTROL,0x7B,0)); logV("Midi panic sent"); } } playing=false; extValuePresent=false; endOfSong=false; // what? stepPlay=0; curOrder=prevOrder; curRow=prevRow; remainingLoops=-1; sPreview.sample=-1; sPreview.wave=-1; sPreview.pos=0; sPreview.dir=false; for (int i=0; inotifyPlaybackStop(); } if (output) if (output->midiOut!=NULL) { output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_STOP,0,0)); for (int i=0; i=0) { output->midiOut->send(TAMidiMessage(0x80|(i&15),chan[i].curMidiNote,0)); } } } // reset all chan oscs for (int i=0; igetOscBuffer(dispatchChanOfChan[i]); if (buf!=NULL) { memset(buf->data,0,65536*sizeof(short)); buf->needle=0; buf->readNeedle=0; } } BUSY_END; } void DivEngine::halt() { BUSY_BEGIN; halted=true; BUSY_END; } void DivEngine::resume() { BUSY_BEGIN; halted=false; haltOn=DIV_HALT_NONE; BUSY_END; } void DivEngine::haltWhen(DivHaltPositions when) { BUSY_BEGIN; halted=false; haltOn=when; BUSY_END; } bool DivEngine::isHalted() { return halted; } const char** DivEngine::getRegisterSheet(int sys) { if (sys<0 || sys>=song.systemLen) return NULL; return disCont[sys].dispatch->getRegisterSheet(); } void DivEngine::recalcChans() { bool isInsTypePossible[DIV_INS_MAX]; chans=0; int chanIndex=0; memset(isInsTypePossible,0,DIV_INS_MAX*sizeof(bool)); for (int i=0; ichanInsType[j][0]!=DIV_INS_NULL) { isInsTypePossible[sysDefs[song.system[i]]->chanInsType[j][0]]=true; } if (sysDefs[song.system[i]]->chanInsType[j][1]!=DIV_INS_NULL) { isInsTypePossible[sysDefs[song.system[i]]->chanInsType[j][1]]=true; } } } } possibleInsTypes.clear(); for (int i=0; imidiOut!=NULL) { output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_STOP,0,0)); for (int i=0; i=0) { output->midiOut->send(TAMidiMessage(0x80|(i&15),chan[i].curMidiNote,0)); } } } for (int i=0; idispatch(DivCommand(DIV_CMD_GET_VOLMAX,dispatchChanOfChan[i]))<<8)|0xff; chan[i].volume=chan[i].volMax; if (song.linearPitch==0) chan[i].vibratoFine=4; } extValue=0; extValuePresent=0; speeds=curSubSong->speeds; firstTick=false; shallStop=false; shallStopSched=false; pendingMetroTick=0; elapsedBars=0; elapsedBeats=0; nextSpeed=speeds.val[0]; divider=curSubSong->hz; globalPitch=0; for (int i=0; ireset(); disCont[i].clear(); } } void DivEngine::syncReset() { BUSY_BEGIN; reset(); BUSY_END; } const int sampleRates[6]={ 4000, 8000, 11025, 16000, 22050, 32000 }; int DivEngine::fileToDivRate(int frate) { if (frate<0) frate=0; if (frate>5) frate=5; return sampleRates[frate]; } int DivEngine::divToFileRate(int drate) { if (drate>26000) { return 5; } else if (drate>18000) { return 4; } else if (drate>14000) { return 3; } else if (drate>9500) { return 2; } else if (drate>6000) { return 1; } else { return 0; } return 4; } void DivEngine::testFunction() { logI("it works!"); } int DivEngine::getEffectiveSampleRate(int rate) { if (rate<1) return 0; switch (song.system[0]) { case DIV_SYSTEM_YMU759: return 8000; case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: return 1278409/(1280000/rate); case DIV_SYSTEM_PCE: return 1789773/(1789773/rate); case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: return (31250*MIN(255,(rate*255/31250)))/255; case DIV_SYSTEM_QSOUND: return (24038*MIN(65535,(rate*4096/24038)))/4096; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: return 18518; case DIV_SYSTEM_VERA: return (48828*MIN(128,(rate*128/48828)))/128; case DIV_SYSTEM_X1_010: return (31250*MIN(255,(rate*16/31250)))/16; // TODO: support variable clock case case DIV_SYSTEM_ES5506: return (31250*MIN(131071,(rate*2048/31250)))/2048; // TODO: support variable clock, channel limit case default: break; } return rate; } void DivEngine::previewSample(int sample, int note, int pStart, int pEnd) { BUSY_BEGIN; previewSampleNoLock(sample,note,pStart,pEnd); BUSY_END; } void DivEngine::stopSamplePreview() { BUSY_BEGIN; stopSamplePreviewNoLock(); BUSY_END; } void DivEngine::previewWave(int wave, int note) { BUSY_BEGIN; previewWaveNoLock(wave,note); BUSY_END; } void DivEngine::stopWavePreview() { BUSY_BEGIN; stopWavePreviewNoLock(); BUSY_END; } void DivEngine::previewSampleNoLock(int sample, int note, int pStart, int pEnd) { sPreview.pBegin=pStart; sPreview.pEnd=pEnd; sPreview.dir=false; if (sample<0 || sample>=(int)song.sample.size()) { sPreview.sample=-1; sPreview.pos=0; sPreview.dir=false; return; } blip_clear(samp_bb); double rate=song.sample[sample]->centerRate; if (note>=0) { rate=(pow(2.0,(double)(note)/12.0)*((double)song.sample[sample]->centerRate)*0.0625); if (rate<=0) rate=song.sample[sample]->centerRate; } if (rate<100) rate=100; double rateOrig=rate; sPreview.rateMul=1; while (sPreview.rateMul<0x40000000 && rate=0)?sPreview.pBegin:0; sPreview.posSub=0; sPreview.sample=sample; sPreview.wave=-1; sPreview.dir=false; } void DivEngine::stopSamplePreviewNoLock() { sPreview.sample=-1; sPreview.pos=0; sPreview.dir=false; } void DivEngine::previewWaveNoLock(int wave, int note) { if (wave<0 || wave>=(int)song.wave.size()) { sPreview.wave=-1; sPreview.pos=0; sPreview.dir=false; return; } if (song.wave[wave]->len<=0) { return; } blip_clear(samp_bb); double rate=song.wave[wave]->len*((song.tuning*0.0625)*pow(2.0,(double)(note+3)/12.0)); if (rate<100) rate=100; double rateOrig=rate; sPreview.rateMul=1; while (sPreview.rateMul<0x40000000 && rate=0 && sPreview.sample<(int)song.sample.size()); } int DivEngine::getSamplePreviewPos() { return sPreview.pos; } double DivEngine::getSamplePreviewRate() { return sPreview.rate; } String DivEngine::getConfigPath() { return configPath; } int DivEngine::getMaxVolumeChan(int ch) { return chan[ch].volMax>>8; } unsigned char DivEngine::getOrder() { return prevOrder; } int DivEngine::getRow() { return prevRow; } int DivEngine::getElapsedBars() { return elapsedBars; } int DivEngine::getElapsedBeats() { return elapsedBeats; } size_t DivEngine::getCurrentSubSong() { return curSubSongIndex; } const DivGroovePattern& DivEngine::getSpeeds() { return speeds; } float DivEngine::getHz() { return curSubSong->hz; } float DivEngine::getCurHz() { return divider; } int DivEngine::getTotalSeconds() { return totalSeconds; } int DivEngine::getTotalTicks() { return totalTicks; } bool DivEngine::getRepeatPattern() { return repeatPattern; } void DivEngine::setRepeatPattern(bool value) { BUSY_BEGIN; repeatPattern=value; BUSY_END; } bool DivEngine::hasExtValue() { return extValuePresent; } unsigned char DivEngine::getExtValue() { return extValue; } bool DivEngine::isPlaying() { return (playing && !freelance); } bool DivEngine::isRunning() { return playing; } bool DivEngine::isStepping() { return !(stepPlay==0); } bool DivEngine::isChannelMuted(int chan) { return isMuted[chan]; } void DivEngine::toggleMute(int chan) { muteChannel(chan,!isMuted[chan]); } void DivEngine::toggleSolo(int chan) { bool solo=false; for (int i=0; imuteChannel(dispatchChanOfChan[i],isMuted[i]); } } } else { for (int i=0; imuteChannel(dispatchChanOfChan[i],isMuted[i]); } } } BUSY_END; } void DivEngine::muteChannel(int chan, bool mute) { BUSY_BEGIN; isMuted[chan]=mute; if (disCont[dispatchOfChan[chan]].dispatch!=NULL) { disCont[dispatchOfChan[chan]].dispatch->muteChannel(dispatchChanOfChan[chan],isMuted[chan]); } BUSY_END; } void DivEngine::unmuteAll() { BUSY_BEGIN; for (int i=0; imuteChannel(dispatchChanOfChan[i],isMuted[i]); } } BUSY_END; } int DivEngine::addInstrument(int refChan, DivInstrumentType fallbackType) { if (song.ins.size()>=256) return -1; BUSY_BEGIN; DivInstrument* ins=new DivInstrument; int insCount=(int)song.ins.size(); DivInstrumentType prefType; if (refChan<0) { prefType=fallbackType; } else { prefType=getPreferInsType(refChan); } switch (prefType) { case DIV_INS_OPLL: *ins=song.nullInsOPLL; break; case DIV_INS_OPL: *ins=song.nullInsOPL; break; case DIV_INS_OPL_DRUMS: *ins=song.nullInsOPLDrums; break; default: break; } if (refChan>=0) { if (sysOfChan[refChan]==DIV_SYSTEM_QSOUND) { *ins=song.nullInsQSound; } } ins->name=fmt::sprintf("Instrument %d",insCount); if (prefType!=DIV_INS_NULL) { ins->type=prefType; } saveLock.lock(); song.ins.push_back(ins); song.insLen=insCount+1; checkAssetDir(song.insDir,song.ins.size()); saveLock.unlock(); BUSY_END; return insCount; } int DivEngine::addInstrumentPtr(DivInstrument* which) { if (song.ins.size()>=256) { delete which; return -1; } BUSY_BEGIN; saveLock.lock(); song.ins.push_back(which); song.insLen=song.ins.size(); checkAssetDir(song.insDir,song.ins.size()); checkAssetDir(song.waveDir,song.wave.size()); checkAssetDir(song.sampleDir,song.sample.size()); saveLock.unlock(); BUSY_END; return song.insLen; } void DivEngine::loadTempIns(DivInstrument* which) { BUSY_BEGIN; if (tempIns==NULL) { tempIns=new DivInstrument; } *tempIns=*which; BUSY_END; } void DivEngine::delInstrument(int index) { BUSY_BEGIN; saveLock.lock(); if (index>=0 && index<(int)song.ins.size()) { for (int i=0; inotifyInsDeletion(song.ins[index]); } delete song.ins[index]; song.ins.erase(song.ins.begin()+index); song.insLen=song.ins.size(); for (int i=0; ipat[i].data[k]==NULL) continue; for (int l=0; lpatLen; l++) { if (song.subsong[j]->pat[i].data[k]->data[l][2]>index) { song.subsong[j]->pat[i].data[k]->data[l][2]--; } } } } } removeAsset(song.insDir,index); checkAssetDir(song.insDir,song.ins.size()); } saveLock.unlock(); BUSY_END; } int DivEngine::addWave() { if (song.wave.size()>=256) { lastError="too many wavetables!"; return -1; } BUSY_BEGIN; saveLock.lock(); DivWavetable* wave=new DivWavetable; int waveCount=(int)song.wave.size(); song.wave.push_back(wave); song.waveLen=waveCount+1; checkAssetDir(song.waveDir,song.wave.size()); saveLock.unlock(); BUSY_END; return waveCount; } int DivEngine::addWavePtr(DivWavetable* which) { if (song.wave.size()>=256) { lastError="too many wavetables!"; delete which; return -1; } BUSY_BEGIN; saveLock.lock(); int waveCount=(int)song.wave.size(); song.wave.push_back(which); song.waveLen=waveCount+1; checkAssetDir(song.waveDir,song.wave.size()); saveLock.unlock(); BUSY_END; return song.waveLen; } DivWavetable* DivEngine::waveFromFile(const char* path, bool addRaw) { FILE* f=ps_fopen(path,"rb"); if (f==NULL) { lastError=fmt::sprintf("%s",strerror(errno)); return NULL; } unsigned char* buf; ssize_t len; if (fseek(f,0,SEEK_END)!=0) { fclose(f); lastError=fmt::sprintf("could not seek to end: %s",strerror(errno)); return NULL; } len=ftell(f); if (len<0) { fclose(f); lastError=fmt::sprintf("could not determine file size: %s",strerror(errno)); return NULL; } if (len==(SIZE_MAX>>1)) { fclose(f); lastError="file size is invalid!"; return NULL; } if (len==0) { fclose(f); lastError="file is empty"; return NULL; } if (fseek(f,0,SEEK_SET)!=0) { fclose(f); lastError=fmt::sprintf("could not seek to beginning: %s",strerror(errno)); return NULL; } buf=new unsigned char[len]; if (fread(buf,1,len,f)!=(size_t)len) { logW("did not read entire wavetable file buffer!"); delete[] buf; lastError=fmt::sprintf("could not read entire file: %s",strerror(errno)); return NULL; } fclose(f); SafeReader reader=SafeReader(buf,len); unsigned char magic[16]; bool isFurnaceTable=false; try { reader.read(magic,16); if (memcmp("-Furnace waveta-",magic,16)==0) { isFurnaceTable=true; } } catch (EndOfFileException& e) { reader.seek(0,SEEK_SET); } DivWavetable* wave=new DivWavetable; try { if (isFurnaceTable) { reader.seek(16,SEEK_SET); short version=reader.readS(); reader.readS(); // reserved reader.seek(20,SEEK_SET); if (wave->readWaveData(reader,version)!=DIV_DATA_SUCCESS) { lastError="invalid wavetable header/data!"; delete wave; delete[] buf; return NULL; } } else { try { // read as .dmw reader.seek(0,SEEK_SET); int len=reader.readI(); logD("wave length %d",len); if (len<=0 || len>256) { throw EndOfFileException(&reader,reader.size()); } wave->len=len; wave->max=(unsigned char)reader.readC(); if (wave->max==255) { // new wavetable format unsigned char waveVersion=reader.readC(); logI("reading modern .dmw..."); logD("wave version %d",waveVersion); wave->max=(unsigned char)reader.readC(); for (int i=0; idata[i]=reader.readI(); } } else if (reader.size()==(size_t)(len+5)) { // read as .dmw logI("reading .dmw..."); if (len>256) len=256; for (int i=0; idata[i]=(unsigned char)reader.readC(); } } else { // read as binary if (addRaw) { logI("reading binary..."); len=reader.size(); if (len>256) len=256; reader.seek(0,SEEK_SET); for (int i=0; idata[i]=(unsigned char)reader.readC(); if (wave->maxdata[i]) wave->max=wave->data[i]; } wave->len=len; } else { delete wave; delete[] buf; return NULL; } } } catch (EndOfFileException& e) { // read as binary if (addRaw) { len=reader.size(); logI("reading binary for being too small..."); if (len>256) len=256; reader.seek(0,SEEK_SET); for (int i=0; idata[i]=(unsigned char)reader.readC(); if (wave->maxdata[i]) wave->max=wave->data[i]; } wave->len=len; } else { delete wave; delete[] buf; return NULL; } } } } catch (EndOfFileException& e) { delete wave; delete[] buf; lastError="premature end of file"; return NULL; } return wave; } void DivEngine::delWave(int index) { BUSY_BEGIN; saveLock.lock(); if (index>=0 && index<(int)song.wave.size()) { delete song.wave[index]; song.wave.erase(song.wave.begin()+index); song.waveLen=song.wave.size(); removeAsset(song.waveDir,index); checkAssetDir(song.waveDir,song.wave.size()); } saveLock.unlock(); BUSY_END; } int DivEngine::addSample() { if (song.sample.size()>=256) { lastError="too many samples!"; return -1; } BUSY_BEGIN; saveLock.lock(); DivSample* sample=new DivSample; int sampleCount=(int)song.sample.size(); sample->name=fmt::sprintf("Sample %d",sampleCount); song.sample.push_back(sample); song.sampleLen=sampleCount+1; sPreview.sample=-1; sPreview.pos=0; sPreview.dir=false; checkAssetDir(song.sampleDir,song.sample.size()); saveLock.unlock(); renderSamples(); BUSY_END; return sampleCount; } int DivEngine::addSamplePtr(DivSample* which) { if (song.sample.size()>=256) { lastError="too many samples!"; delete which; return -1; } int sampleCount=(int)song.sample.size(); BUSY_BEGIN; saveLock.lock(); song.sample.push_back(which); song.sampleLen=sampleCount+1; checkAssetDir(song.sampleDir,song.sample.size()); saveLock.unlock(); renderSamples(); BUSY_END; return sampleCount; } void DivEngine::delSample(int index) { BUSY_BEGIN; sPreview.sample=-1; sPreview.pos=0; sPreview.dir=false; saveLock.lock(); if (index>=0 && index<(int)song.sample.size()) { delete song.sample[index]; song.sample.erase(song.sample.begin()+index); song.sampleLen=song.sample.size(); removeAsset(song.sampleDir,index); checkAssetDir(song.sampleDir,song.sample.size()); // compensate for (DivInstrument* i: song.ins) { if (i->amiga.initSample==index) { i->amiga.initSample=-1; } else if (i->amiga.initSample>index) { i->amiga.initSample--; } for (int j=0; j<120; j++) { if (i->amiga.noteMap[j].map==index) { i->amiga.noteMap[j].map=-1; } else if (i->amiga.noteMap[j].map>index) { i->amiga.noteMap[j].map--; } } } renderSamples(); } saveLock.unlock(); BUSY_END; } void DivEngine::addOrder(int pos, bool duplicate, bool where) { unsigned char order[DIV_MAX_CHANS]; if (curSubSong->ordersLen>=(DIV_MAX_PATTERNS-1)) return; memset(order,0,DIV_MAX_CHANS); BUSY_BEGIN_SOFT; if (duplicate) { for (int i=0; iord[i][pos]; } } else { bool used[DIV_MAX_PATTERNS]; for (int i=0; iordersLen; j++) { used[curOrders->ord[i][j]]=true; } order[i]=(DIV_MAX_PATTERNS-1); for (int j=0; jord[i][curSubSong->ordersLen]=order[i]; } curSubSong->ordersLen++; saveLock.unlock(); } else { // after current order saveLock.lock(); for (int i=0; iordersLen; j>pos; j--) { curOrders->ord[i][j]=curOrders->ord[i][j-1]; } curOrders->ord[i][pos+1]=order[i]; } curSubSong->ordersLen++; saveLock.unlock(); curOrder=pos+1; prevOrder=curOrder; if (playing && !freelance) { playSub(false); } } BUSY_END; } void DivEngine::deepCloneOrder(int pos, bool where) { unsigned char order[DIV_MAX_CHANS]; if (curSubSong->ordersLen>=(DIV_MAX_PATTERNS-1)) return; warnings=""; BUSY_BEGIN_SOFT; for (int i=0; iord[i][pos]; // find free slot for (int j=0; jdata,oldPat->data,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short)); logD("found at %d",j); didNotFind=false; break; } } if (didNotFind) { addWarning(fmt::sprintf("no free patterns in channel %d!",i)); } } if (where) { // at the end saveLock.lock(); for (int i=0; iord[i][curSubSong->ordersLen]=order[i]; } curSubSong->ordersLen++; saveLock.unlock(); } else { // after current order saveLock.lock(); for (int i=0; iordersLen; j>pos; j--) { curOrders->ord[i][j]=curOrders->ord[i][j-1]; } curOrders->ord[i][pos+1]=order[i]; } curSubSong->ordersLen++; saveLock.unlock(); if (pos<=curOrder) curOrder++; if (playing && !freelance) { playSub(false); } } BUSY_END; } void DivEngine::deleteOrder(int pos) { if (curSubSong->ordersLen<=1) return; BUSY_BEGIN_SOFT; saveLock.lock(); for (int i=0; iordersLen; j++) { curOrders->ord[i][j]=curOrders->ord[i][j+1]; } } curSubSong->ordersLen--; saveLock.unlock(); if (curOrder>pos) curOrder--; if (curOrder>=curSubSong->ordersLen) curOrder=curSubSong->ordersLen-1; if (playing && !freelance) { playSub(false); } BUSY_END; } void DivEngine::moveOrderUp(int& pos) { BUSY_BEGIN_SOFT; if (pos<1) { BUSY_END; return; } saveLock.lock(); for (int i=0; iord[i][pos]^=curOrders->ord[i][pos-1]; curOrders->ord[i][pos-1]^=curOrders->ord[i][pos]; curOrders->ord[i][pos]^=curOrders->ord[i][pos-1]; } saveLock.unlock(); if (curOrder==pos) { curOrder--; } pos--; if (playing && !freelance) { playSub(false); } BUSY_END; } void DivEngine::moveOrderDown(int& pos) { BUSY_BEGIN_SOFT; if (pos>=curSubSong->ordersLen-1) { BUSY_END; return; } saveLock.lock(); for (int i=0; iord[i][pos]^=curOrders->ord[i][pos+1]; curOrders->ord[i][pos+1]^=curOrders->ord[i][pos]; curOrders->ord[i][pos]^=curOrders->ord[i][pos+1]; } saveLock.unlock(); if (curOrder==pos) { curOrder++; } pos++; if (playing && !freelance) { playSub(false); } BUSY_END; } void DivEngine::exchangeIns(int one, int two) { for (int i=0; ipat[i].data[k]==NULL) continue; for (int l=0; lpatLen; l++) { if (song.subsong[j]->pat[i].data[k]->data[l][2]==one) { song.subsong[j]->pat[i].data[k]->data[l][2]=two; } else if (song.subsong[j]->pat[i].data[k]->data[l][2]==two) { song.subsong[j]->pat[i].data[k]->data[l][2]=one; } } } } } } void DivEngine::exchangeWave(int one, int two) { // TODO } void DivEngine::exchangeSample(int one, int two) { for (DivInstrument* i: song.ins) { if (i->amiga.initSample==one) { i->amiga.initSample=two; } else if (i->amiga.initSample==two) { i->amiga.initSample=one; } for (int j=0; j<120; j++) { if (i->amiga.noteMap[j].map==one) { i->amiga.noteMap[j].map=two; } else if (i->amiga.noteMap[j].map==two) { i->amiga.noteMap[j].map=one; } } } } bool DivEngine::moveInsUp(int which) { if (which<1 || which>=(int)song.ins.size()) return false; BUSY_BEGIN; DivInstrument* prev=song.ins[which]; saveLock.lock(); song.ins[which]=song.ins[which-1]; song.ins[which-1]=prev; moveAsset(song.insDir,which,which-1); exchangeIns(which,which-1); saveLock.unlock(); BUSY_END; return true; } bool DivEngine::moveWaveUp(int which) { if (which<1 || which>=(int)song.wave.size()) return false; BUSY_BEGIN; DivWavetable* prev=song.wave[which]; saveLock.lock(); song.wave[which]=song.wave[which-1]; song.wave[which-1]=prev; moveAsset(song.waveDir,which,which-1); exchangeWave(which,which-1); saveLock.unlock(); BUSY_END; return true; } bool DivEngine::moveSampleUp(int which) { if (which<1 || which>=(int)song.sample.size()) return false; BUSY_BEGIN; sPreview.sample=-1; sPreview.pos=0; sPreview.dir=false; DivSample* prev=song.sample[which]; saveLock.lock(); song.sample[which]=song.sample[which-1]; song.sample[which-1]=prev; moveAsset(song.sampleDir,which,which-1); exchangeSample(which,which-1); saveLock.unlock(); renderSamples(); BUSY_END; return true; } bool DivEngine::moveInsDown(int which) { if (which<0 || which>=((int)song.ins.size())-1) return false; BUSY_BEGIN; DivInstrument* prev=song.ins[which]; saveLock.lock(); song.ins[which]=song.ins[which+1]; song.ins[which+1]=prev; exchangeIns(which,which+1); moveAsset(song.insDir,which,which+1); saveLock.unlock(); BUSY_END; return true; } bool DivEngine::moveWaveDown(int which) { if (which<0 || which>=((int)song.wave.size())-1) return false; BUSY_BEGIN; DivWavetable* prev=song.wave[which]; saveLock.lock(); song.wave[which]=song.wave[which+1]; song.wave[which+1]=prev; exchangeWave(which,which+1); moveAsset(song.waveDir,which,which+1); saveLock.unlock(); BUSY_END; return true; } bool DivEngine::moveSampleDown(int which) { if (which<0 || which>=((int)song.sample.size())-1) return false; BUSY_BEGIN; sPreview.sample=-1; sPreview.pos=0; sPreview.dir=false; DivSample* prev=song.sample[which]; saveLock.lock(); song.sample[which]=song.sample[which+1]; song.sample[which+1]=prev; exchangeSample(which,which+1); moveAsset(song.sampleDir,which,which+1); saveLock.unlock(); renderSamples(); BUSY_END; return true; } void DivEngine::autoPatchbay() { song.patchbay.clear(); for (unsigned int i=0; igetOutputCount(); if (outs>16) outs=16; if (outs<2) { song.patchbay.reserve(DIV_MAX_OUTPUTS); for (unsigned int j=0; j=chans) return; BUSY_BEGIN; pendingNotes.push_back(DivNoteEvent(chan,ins,note,vol,true)); if (!playing) { reset(); freelance=true; playing=true; } BUSY_END; } void DivEngine::noteOff(int chan) { if (chan<0 || chan>=chans) return; BUSY_BEGIN; pendingNotes.push_back(DivNoteEvent(chan,-1,-1,-1,false)); if (!playing) { reset(); freelance=true; playing=true; } BUSY_END; } void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) { bool isViable[DIV_MAX_CHANS]; bool canPlayAnyway=false; bool notInViableChannel=false; if (midiBaseChan<0) midiBaseChan=0; if (midiBaseChan>=chans) midiBaseChan=chans-1; int finalChan=midiBaseChan; int finalChanType=getChannelType(finalChan); if (!playing) { reset(); freelance=true; playing=true; } // 1. check which channels are viable for this instrument DivInstrument* insInst=getIns(ins); if (getPreferInsType(finalChan)!=insInst->type && getPreferInsSecondType(finalChan)!=insInst->type && getPreferInsType(finalChan)!=DIV_INS_NULL) notInViableChannel=true; for (int i=0; i=song.insLen || getPreferInsType(i)==insInst->type || (getPreferInsType(i)==DIV_INS_NULL && finalChanType==DIV_CH_NOISE) || getPreferInsSecondType(i)==insInst->type) { if (insInst->type==DIV_INS_OPL) { if (insInst->fm.ops==2 || getChannelType(i)==DIV_CH_OP) { isViable[i]=true; canPlayAnyway=true; } else { isViable[i]=false; } } else { isViable[i]=true; canPlayAnyway=true; } } else { isViable[i]=false; } } if (!canPlayAnyway) return; // 2. find a free channel do { if ((!midiPoly) || (isViable[finalChan] && chan[finalChan].midiNote==-1 && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType || notInViableChannel))) { chan[finalChan].midiNote=note; chan[finalChan].midiAge=midiAgeCounter++; pendingNotes.push_back(DivNoteEvent(finalChan,ins,note,vol,true)); return; } if (++finalChan>=chans) { finalChan=0; } } while (finalChan!=midiBaseChan); // 3. find the oldest channel int candidate=finalChan; do { if (isViable[finalChan] && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType || notInViableChannel) && chan[finalChan].midiAge=chans) { finalChan=0; } } while (finalChan!=midiBaseChan); chan[candidate].midiNote=note; chan[candidate].midiAge=midiAgeCounter++; pendingNotes.push_back(DivNoteEvent(candidate,ins,note,vol,true)); } void DivEngine::autoNoteOff(int ch, int note, int vol) { if (!playing) { return; } //if (ch<0 || ch>=chans) return; for (int i=0; i=curSubSong->ordersLen) curOrder=0; prevOrder=curOrder; if (playing && !freelance) { playSub(false); } BUSY_END; } void DivEngine::updateSysFlags(int system, bool restart) { BUSY_BEGIN_SOFT; disCont[system].dispatch->setFlags(song.systemFlags[system]); disCont[system].setRates(got.rate); // patchbay if (song.patchbayAuto) { saveLock.lock(); autoPatchbay(); saveLock.unlock(); } if (restart) { if (isPlaying()) { playSub(false); } else if (freelance) { reset(); } } BUSY_END; } void DivEngine::setSongRate(float hz) { BUSY_BEGIN; saveLock.lock(); curSubSong->hz=hz; divider=curSubSong->hz; saveLock.unlock(); BUSY_END; } void DivEngine::setAudio(DivAudioEngines which) { audioEngine=which; } void DivEngine::setView(DivStatusView which) { view=which; } bool DivEngine::getMetronome() { return metronome; } void DivEngine::setMetronome(bool enable) { metronome=enable; metroAmp=0; } void DivEngine::setMetronomeVol(float vol) { metroVol=vol; } void DivEngine::setConsoleMode(bool enable) { consoleMode=enable; } bool DivEngine::switchMaster(bool full) { logI("switching output..."); deinitAudioBackend(true); if (full) { quitDispatch(); initDispatch(); } if (initAudioBackend()) { for (int i=0; isetRun(true)) { logE("error while activating audio!"); return false; } } else { return false; } renderSamples(); return true; } void DivEngine::setMidiBaseChan(int chan) { if (chan<0 || chan>=chans) chan=0; midiBaseChan=chan; } void DivEngine::setMidiDirect(bool value) { midiIsDirect=value; } void DivEngine::setMidiCallback(std::function what) { midiCallback=what; } bool DivEngine::sendMidiMessage(TAMidiMessage& msg) { if (output==NULL) { logW("output is NULL!"); return false; } if (output->midiOut==NULL) { logW("MIDI output is NULL!"); return false; } BUSY_BEGIN; logD("sending MIDI message..."); bool ret=(output->midiOut->send(msg)); BUSY_END; return ret; } void DivEngine::synchronized(const std::function& what) { BUSY_BEGIN; what(); BUSY_END; } void DivEngine::lockSave(const std::function& what) { saveLock.lock(); what(); saveLock.unlock(); } void DivEngine::lockEngine(const std::function& what) { BUSY_BEGIN; saveLock.lock(); what(); saveLock.unlock(); BUSY_END; } TAAudioDesc& DivEngine::getAudioDescWant() { return want; } TAAudioDesc& DivEngine::getAudioDescGot() { return got; } std::vector& DivEngine::getAudioDevices() { return audioDevs; } std::vector& DivEngine::getMidiIns() { return midiIns; } std::vector& DivEngine::getMidiOuts() { return midiOuts; } void DivEngine::rescanAudioDevices() { audioDevs.clear(); if (output!=NULL) { audioDevs=output->listAudioDevices(); if (output->midiIn!=NULL) { midiIns=output->midiIn->listDevices(); } if (output->midiOut!=NULL) { midiOuts=output->midiOut->listDevices(); } } } void DivEngine::initDispatch(bool isRender) { BUSY_BEGIN; logV("initializing dispatch..."); if (isRender) logI("render cores set"); for (int i=0; i2.0f) metroVol=2.0f; if (lowLatency) logI("using low latency mode."); switch (audioEngine) { case DIV_AUDIO_JACK: #ifndef HAVE_JACK logE("Furnace was not compiled with JACK support!"); setConf("audioEngine","SDL"); saveConf(); #ifdef HAVE_SDL2 output=new TAAudioSDL; #else logE("Furnace was not compiled with SDL support either!"); output=new TAAudio; #endif #else output=new TAAudioJACK; #endif break; case DIV_AUDIO_PORTAUDIO: #ifndef HAVE_PA logE("Furnace was not compiled with PortAudio!"); setConf("audioEngine","SDL"); saveConf(); #ifdef HAVE_SDL2 output=new TAAudioSDL; #else logE("Furnace was not compiled with SDL support either!"); output=new TAAudio; #endif #else output=new TAAudioPA; #endif break; case DIV_AUDIO_SDL: #ifdef HAVE_SDL2 output=new TAAudioSDL; #else logE("Furnace was not compiled with SDL support!"); output=new TAAudio; #endif break; case DIV_AUDIO_DUMMY: output=new TAAudio; break; default: logE("invalid audio engine!"); return false; } logV("listing audio devices"); audioDevs=output->listAudioDevices(); want.deviceName=getConfString("audioDevice",""); want.bufsize=getConfInt("audioBufSize",1024); want.rate=getConfInt("audioRate",44100); want.fragments=2; want.inChans=0; want.outChans=getConfInt("audioChans",2); want.outFormat=TA_AUDIO_FORMAT_F32; want.name="Furnace"; if (want.outChans<1) want.outChans=1; if (want.outChans>16) want.outChans=16; logV("setting callback"); output->setCallback(process,this); logV("calling init"); if (!output->init(want,got)) { logE("error while initializing audio!"); delete output; output=NULL; audioEngine=DIV_AUDIO_NULL; return false; } logV("allocating oscBuf..."); for (int i=0; iinitMidi(false)) { midiIns=output->midiIn->listDevices(); midiOuts=output->midiOut->listDevices(); } else { logW("error while initializing MIDI!"); } if (output->midiIn) { String inName=getConfString("midiInDevice",""); if (!inName.empty()) { // try opening device logI("opening MIDI input."); if (!output->midiIn->openDevice(inName)) { logW("could not open MIDI input device!"); } } else { logV("no MIDI input device selected."); } } if (output->midiOut) { String outName=getConfString("midiOutDevice",""); if (!outName.empty()) { // try opening device logI("opening MIDI output."); if (!output->midiOut->openDevice(outName)) { logW("could not open MIDI output device!"); } } else { logV("no MIDI output device selected."); } } logV("initAudioBackend done"); return true; } bool DivEngine::deinitAudioBackend(bool dueToSwitchMaster) { if (output!=NULL) { logI("closing audio output."); output->quit(); if (output->midiIn) { if (output->midiIn->isDeviceOpen()) { logI("closing MIDI input."); output->midiIn->closeDevice(); } } if (output->midiOut) { if (output->midiOut->isDeviceOpen()) { logI("closing MIDI output."); output->midiOut->closeDevice(); } } output->quitMidi(); delete output; output=NULL; if (dueToSwitchMaster) { audioEngine=DIV_AUDIO_NULL; } } return true; } void DivEngine::preInit() { // register systems if (!systemsRegistered) registerSystems(); // init config initConfDir(); logD("config path: %s",configPath.c_str()); String logPath=configPath+DIR_SEPARATOR_STR+"furnace.log"; startLogFile(logPath.c_str()); logI("Furnace version " DIV_VERSION "."); loadConf(); #ifdef HAVE_SDL2 String audioDriver=getConfString("sdlAudioDriver",""); if (!audioDriver.empty()) { SDL_SetHint("SDL_HINT_AUDIODRIVER",audioDriver.c_str()); } #endif } bool DivEngine::init() { loadSampleROMs(); // set default system preset if (!hasLoadedSomething) { logD("setting default preset"); String preset=getConfString("initialSys2",""); bool oldVol=getConfInt("configVersion",DIV_ENGINE_VERSION)<135; if (preset.empty()) { // try loading old preset logD("trying to load old preset"); preset=decodeSysDesc(getConfString("initialSys","")); oldVol=false; } logD("preset size %ld",preset.size()); if (preset.size()>0 && (preset.size()&3)==0) { initSongWithDesc(preset.c_str(),true,oldVol); } String sysName=getConfString("initialSysName",""); if (sysName=="") { song.systemName=getSongSystemLegacyName(song,!getConfInt("noMultiSystem",0)); } else { song.systemName=sysName; } hasLoadedSomething=true; } // init the rest of engine bool haveAudio=false; if (!initAudioBackend()) { logE("no audio output available!"); } else { haveAudio=true; } logV("creating blip_buf"); samp_bb=blip_new(32768); if (samp_bb==NULL) { logE("not enough memory!"); return false; } samp_bbOut=new short[32768]; samp_bbIn=new short[32768]; samp_bbInLen=32768; metroBuf=new float[8192]; metroBufLen=8192; logV("setting blip rate of samp_bb (%f)",got.rate); blip_set_rates(samp_bb,44100,got.rate); for (int i=0; i<64; i++) { vibTable[i]=127*sin(((double)i/64.0)*(2*M_PI)); } for (int i=0; i<128; i++) { tremTable[i]=255*0.5*(1.0-cos(((double)i/128.0)*(2*M_PI))); } for (int i=0; i<4096; i++) { reversePitchTable[i]=round(1024.0*pow(2.0,(2048.0-(double)i)/(12.0*128.0))); pitchTable[i]=round(1024.0*pow(2.0,((double)i-2048.0)/(12.0*128.0))); } for (int i=0; isetRun(true)) { logE("error while activating!"); return false; } } return true; } bool DivEngine::quit() { deinitAudioBackend(); quitDispatch(); logI("saving config."); saveConf(); active=false; for (int i=0; i