/** * 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. */ #include "engine.h" #include "../ta-log.h" #include "../fileutils.h" #ifdef HAVE_SNDFILE #include "sfWrapper.h" #endif DivSample* DivEngine::sampleFromFile(const char* path) { if (song.sample.size()>=256) { lastError="too many samples!"; return NULL; } BUSY_BEGIN; warnings=""; const char* pathRedux=strrchr(path,DIR_SEPARATOR); if (pathRedux==NULL) { pathRedux=path; } else { pathRedux++; } String stripPath; const char* pathReduxEnd=strrchr(pathRedux,'.'); if (pathReduxEnd==NULL) { stripPath=pathRedux; } else { for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) { stripPath+=*i; } } const char* ext=strrchr(path,'.'); if (ext!=NULL) { String extS; for (; *ext; ext++) { char i=*ext; if (i>='A' && i<='Z') { i+='a'-'A'; } extS+=i; } if (extS==".dmc" || extS==".brr") { // read as .dmc or .brr size_t len=0; DivSample* sample=new DivSample; sample->name=stripPath; FILE* f=ps_fopen(path,"rb"); if (f==NULL) { BUSY_END; lastError=fmt::sprintf("could not open file! (%s)",strerror(errno)); delete sample; return NULL; } if (fseek(f,0,SEEK_END)<0) { fclose(f); BUSY_END; lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno)); delete sample; return NULL; } len=ftell(f); if (len==0) { fclose(f); BUSY_END; lastError="file is empty!"; delete sample; return NULL; } if (len==(SIZE_MAX>>1)) { fclose(f); BUSY_END; lastError="file is invalid!"; delete sample; return NULL; } if (fseek(f,0,SEEK_SET)<0) { fclose(f); BUSY_END; lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno)); delete sample; return NULL; } if (extS==".dmc") { sample->rate=33144; sample->centerRate=33144; sample->depth=DIV_SAMPLE_DEPTH_1BIT_DPCM; sample->init(len*8); } else if (extS==".brr") { sample->rate=32000; sample->centerRate=32000; sample->depth=DIV_SAMPLE_DEPTH_BRR; sample->init(16*(len/9)); } else { fclose(f); BUSY_END; lastError="wait... is that right? no I don't think so..."; delete sample; return NULL; } unsigned char* dataBuf=sample->dataDPCM; if (extS==".brr") { dataBuf=sample->dataBRR; if ((len%9)==2) { // read loop position unsigned short loopPos=0; logD("BRR file has loop position"); if (fread(&loopPos,1,2,f)!=2) { logW("could not read loop position! %s",strerror(errno)); } else { #ifdef TA_BIG_ENDIAN loopPos=(loopPos>>8)|(loopPos<<8); #endif sample->loopStart=16*(loopPos/9); sample->loopEnd=sample->samples; sample->loop=true; sample->loopMode=DIV_SAMPLE_LOOP_FORWARD; } len-=2; if (len==0) { fclose(f); BUSY_END; lastError="BRR sample is empty!"; delete sample; return NULL; } } else if ((len%9)!=0) { fclose(f); BUSY_END; lastError="possibly corrupt BRR sample!"; delete sample; return NULL; } } if (fread(dataBuf,1,len,f)==0) { fclose(f); BUSY_END; lastError=fmt::sprintf("could not read file! (%s)",strerror(errno)); delete sample; return NULL; } BUSY_END; return sample; } } #ifndef HAVE_SNDFILE lastError="Furnace was not compiled with libsndfile!"; return NULL; #else SF_INFO si; SFWrapper sfWrap; memset(&si,0,sizeof(SF_INFO)); SNDFILE* f=sfWrap.doOpen(path,SFM_READ,&si); if (f==NULL) { BUSY_END; int err=sf_error(NULL); if (err==SF_ERR_SYSTEM) { lastError=fmt::sprintf("could not open file! (%s %s)",sf_error_number(err),strerror(errno)); } else { lastError=fmt::sprintf("could not open file! (%s)\nif this is raw sample data, you may import it by right-clicking the Load Sample icon and selecting \"import raw\".",sf_error_number(err)); } return NULL; } if (si.frames>16777215) { lastError="this sample is too big! max sample size is 16777215."; sfWrap.doClose(); BUSY_END; return NULL; } void* buf=NULL; sf_count_t sampleLen=sizeof(short); if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { logD("sample is 8-bit unsigned"); buf=new unsigned char[si.channels*si.frames]; sampleLen=sizeof(unsigned char); } else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { logD("sample is 32-bit float"); buf=new float[si.channels*si.frames]; sampleLen=sizeof(float); } else { logD("sample is 16-bit signed"); buf=new short[si.channels*si.frames]; sampleLen=sizeof(short); } if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8 || (si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { if (sf_read_raw(f,buf,si.frames*si.channels*sampleLen)!=(si.frames*si.channels*sampleLen)) { logW("sample read size mismatch!"); } } else { if (sf_read_short(f,(short*)buf,si.frames*si.channels)!=(si.frames*si.channels)) { logW("sample read size mismatch!"); } } DivSample* sample=new DivSample; int sampleCount=(int)song.sample.size(); sample->name=stripPath; int index=0; if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { sample->depth=DIV_SAMPLE_DEPTH_8BIT; } else { sample->depth=DIV_SAMPLE_DEPTH_16BIT; } sample->init(si.frames); if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { for (int i=0; idata8[index++]=averaged; } delete[] (unsigned char*)buf; } else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { for (int i=0; i32767.0) averaged=32767.0; sample->data16[index++]=averaged; } delete[] (float*)buf; } else { for (int i=0; idata16[index++]=averaged; } delete[] (short*)buf; } sample->rate=si.samplerate; if (sample->rate<4000) sample->rate=4000; if (sample->rate>96000) sample->rate=96000; sample->centerRate=si.samplerate; SF_INSTRUMENT inst; if (sf_command(f, SFC_GET_INSTRUMENT, &inst, sizeof(inst)) == SF_TRUE) { // There's no documentation on libsndfile detune range, but the code // implies -50..50. Yet when loading a file you can get a >50 value. if(inst.detune > 50) inst.detune = inst.detune - 100; short pitch = ((0x3c-inst.basenote)*100) + inst.detune; sample->centerRate=si.samplerate*pow(2.0,pitch/(12.0 * 100.0)); if(inst.loop_count && inst.loops[0].mode >= SF_LOOP_FORWARD) { sample->loop=true; sample->loopMode=(DivSampleLoopMode)(inst.loops[0].mode-SF_LOOP_FORWARD); sample->loopStart=inst.loops[0].start; sample->loopEnd=inst.loops[0].end; if(inst.loops[0].end < (unsigned int)sampleCount) sampleCount=inst.loops[0].end; } else sample->loop=false; } if (sample->centerRate<4000) sample->centerRate=4000; if (sample->centerRate>64000) sample->centerRate=64000; sfWrap.doClose(); BUSY_END; return sample; #endif } DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign, bool swapNibbles) { if (song.sample.size()>=256) { lastError="too many samples!"; return NULL; } if (channels<1) { channels=1; } if (depth!=DIV_SAMPLE_DEPTH_8BIT && depth!=DIV_SAMPLE_DEPTH_16BIT) { if (channels!=1) { channels=1; } } BUSY_BEGIN; warnings=""; const char* pathRedux=strrchr(path,DIR_SEPARATOR); if (pathRedux==NULL) { pathRedux=path; } else { pathRedux++; } String stripPath; const char* pathReduxEnd=strrchr(pathRedux,'.'); if (pathReduxEnd==NULL) { stripPath=pathRedux; } else { for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) { stripPath+=*i; } } size_t len=0; size_t lenDivided=0; DivSample* sample=new DivSample; sample->name=stripPath; FILE* f=ps_fopen(path,"rb"); if (f==NULL) { BUSY_END; lastError=fmt::sprintf("could not open file! (%s)",strerror(errno)); delete sample; return NULL; } if (fseek(f,0,SEEK_END)<0) { fclose(f); BUSY_END; lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno)); delete sample; return NULL; } len=ftell(f); if (len==0) { fclose(f); BUSY_END; lastError="file is empty!"; delete sample; return NULL; } if (len==(SIZE_MAX>>1)) { fclose(f); BUSY_END; lastError="file is invalid!"; delete sample; return NULL; } if (fseek(f,0,SEEK_SET)<0) { fclose(f); BUSY_END; lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno)); delete sample; return NULL; } lenDivided=len/channels; unsigned int samples=lenDivided; switch (depth) { case DIV_SAMPLE_DEPTH_1BIT: case DIV_SAMPLE_DEPTH_1BIT_DPCM: samples=lenDivided*8; break; case DIV_SAMPLE_DEPTH_YMZ_ADPCM: case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: case DIV_SAMPLE_DEPTH_ADPCM_A: case DIV_SAMPLE_DEPTH_ADPCM_B: case DIV_SAMPLE_DEPTH_ADPCM_K: case DIV_SAMPLE_DEPTH_VOX: samples=lenDivided*2; break; case DIV_SAMPLE_DEPTH_8BIT: case DIV_SAMPLE_DEPTH_MULAW: case DIV_SAMPLE_DEPTH_C219: samples=lenDivided; break; case DIV_SAMPLE_DEPTH_BRR: samples=16*((lenDivided+8)/9); break; case DIV_SAMPLE_DEPTH_16BIT: samples=(lenDivided+1)/2; break; default: break; } if (samples>16777215) { fclose(f); BUSY_END; lastError="this sample is too big! max sample size is 16777215."; delete sample; return NULL; } sample->rate=32000; sample->centerRate=32000; sample->depth=depth; sample->init(samples); unsigned char* buf=new unsigned char[len]; if (fread(buf,1,len,f)==0) { fclose(f); BUSY_END; lastError=fmt::sprintf("could not read file! (%s)",strerror(errno)); delete[] buf; delete sample; return NULL; } fclose(f); // import sample size_t pos=0; if (depth==DIV_SAMPLE_DEPTH_16BIT) { for (unsigned int i=0; i=len) break; if (bigEndian) { accum+=(short)(((short)((buf[pos]<<8)|buf[pos+1]))^(unsign?0x8000:0)); } else { accum+=(short)(((short)(buf[pos]|(buf[pos+1]<<8)))^(unsign?0x8000:0)); } pos+=2; } accum/=channels; sample->data16[i]=accum; } } else if (depth==DIV_SAMPLE_DEPTH_8BIT) { for (unsigned int i=0; i=len) break; accum+=(signed char)(buf[pos++]^(unsign?0x80:0)); } accum/=channels; sample->data8[i]=accum; } } else { memcpy(sample->getCurBuf(),buf,len); } delete[] buf; if (swapNibbles) { unsigned char* b=(unsigned char*)sample->getCurBuf(); switch (depth) { case DIV_SAMPLE_DEPTH_1BIT: case DIV_SAMPLE_DEPTH_1BIT_DPCM: // reverse bit order for (unsigned int i=0; igetCurBufLen(); i++) { b[i]=( ((b[i]&128)?1:0)| ((b[i]&64)?2:0)| ((b[i]&32)?4:0)| ((b[i]&16)?8:0)| ((b[i]&8)?16:0)| ((b[i]&4)?32:0)| ((b[i]&2)?64:0)| ((b[i]&1)?128:0) ); } break; case DIV_SAMPLE_DEPTH_YMZ_ADPCM: case DIV_SAMPLE_DEPTH_QSOUND_ADPCM: case DIV_SAMPLE_DEPTH_ADPCM_A: case DIV_SAMPLE_DEPTH_ADPCM_B: case DIV_SAMPLE_DEPTH_ADPCM_K: case DIV_SAMPLE_DEPTH_VOX: // swap nibbles for (unsigned int i=0; igetCurBufLen(); i++) { b[i]=(b[i]<<4)|(b[i]>>4); } break; case DIV_SAMPLE_DEPTH_MULAW: // Namco to G.711 // Namco: smmmmxxx // G.711: sxxxmmmm (^0xff) for (unsigned int i=0; igetCurBufLen(); i++) { b[i]=(((b[i]&7)<<4)|(((b[i]>>3)&15)^((b[i]&0x80)?15:0))|(b[i]&0x80))^0xff; } break; default: break; } } BUSY_END; return sample; }