528 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			528 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /**
 | |
|  * Furnace Tracker - multi-system chiptune tracker
 | |
|  * Copyright (C) 2021-2024 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; i<si.frames*si.channels; i+=si.channels) {
 | |
|       int averaged=0;
 | |
|       for (int j=0; j<si.channels; j++) {
 | |
|         averaged+=((int)((unsigned char*)buf)[i+j])-128;
 | |
|       }
 | |
|       averaged/=si.channels;
 | |
|       sample->data8[index++]=averaged;
 | |
|     }
 | |
|     delete[] (unsigned char*)buf;
 | |
|   } else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT)  {
 | |
|     for (int i=0; i<si.frames*si.channels; i+=si.channels) {
 | |
|       float averaged=0.0f;
 | |
|       for (int j=0; j<si.channels; j++) {
 | |
|         averaged+=((float*)buf)[i+j];
 | |
|       }
 | |
|       averaged/=si.channels;
 | |
|       averaged*=32767.0;
 | |
|       if (averaged<-32768.0) averaged=-32768.0;
 | |
|       if (averaged>32767.0) averaged=32767.0;
 | |
|       sample->data16[index++]=averaged;
 | |
|     }
 | |
|     delete[] (float*)buf;
 | |
|   } else {
 | |
|     for (int i=0; i<si.frames*si.channels; i+=si.channels) {
 | |
|       int averaged=0;
 | |
|       for (int j=0; j<si.channels; j++) {
 | |
|         averaged+=((short*)buf)[i+j];
 | |
|       }
 | |
|       averaged/=si.channels;
 | |
|       sample->data16[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.
 | |
|     // disabled for now
 | |
|     /*
 | |
|     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, int rate) {
 | |
|   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_IMA_ADPCM:
 | |
|       samples=(lenDivided-4)*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=rate;
 | |
|   sample->centerRate=rate;
 | |
|   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<samples; i++) {
 | |
|       int accum=0;
 | |
|       for (int j=0; j<channels; j++) {
 | |
|         if (pos+1>=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<samples; i++) {
 | |
|       int accum=0;
 | |
|       for (int j=0; j<channels; j++) {
 | |
|         if (pos>=len) break;
 | |
|         accum+=(signed char)(buf[pos++]^(unsign?0x80:0));
 | |
|       }
 | |
|       accum/=channels;
 | |
|       sample->data8[i]=accum;
 | |
|     }
 | |
|     if (bigEndian) {
 | |
|       for (unsigned int i=0; (i+1)<samples; i+=2) {
 | |
|         sample->data8[i]^=sample->data8[i^1];
 | |
|         sample->data8[i^1]^=sample->data8[i];
 | |
|         sample->data8[i]^=sample->data8[i^1];
 | |
|       }
 | |
|     }
 | |
|   } 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; i<sample->getCurBufLen(); 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; i<sample->getCurBufLen(); 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; i<sample->getCurBufLen(); 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;
 | |
| }
 | 
