diff --git a/CMakeLists.txt b/CMakeLists.txt index dd63fec2d..6116dba8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -282,6 +282,7 @@ src/engine/config.cpp src/engine/dispatchContainer.cpp src/engine/engine.cpp src/engine/fileOps.cpp +src/engine/filter.cpp src/engine/instrument.cpp src/engine/macroInt.cpp src/engine/pattern.cpp diff --git a/src/engine/filter.cpp b/src/engine/filter.cpp new file mode 100644 index 000000000..729c8caa1 --- /dev/null +++ b/src/engine/filter.cpp @@ -0,0 +1,88 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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 +#define _USE_MATH_DEFINES +#include +#include "filter.h" +#include "../ta-log.h" + +float* DivFilterTables::cubicTable=NULL; +float* DivFilterTables::sincTable=NULL; +float* DivFilterTables::sincIntegralTable=NULL; + +// portions from Schism Tracker (scripts/lutgen.c) +// licensed under same license as this program. +float* DivFilterTables::getCubicTable() { + if (cubicTable==NULL) { + logD("initializing cubic spline table.\n"); + cubicTable=new float[4096]; + + for (int i=0; i<1024; i++) { + float x=(float)i/1024.0; + cubicTable[(i<<2)]=-0.5*pow(x,3)+1.0*pow(x,2)-0.5*x; + cubicTable[1+(i<<2)]=1.5*pow(x,3)-2.5*pow(x,2)+1.0; + cubicTable[2+(i<<2)]=-1.5*pow(x,3)+2.0*pow(x,2)+0.5*x; + cubicTable[3+(i<<2)]=0.5*pow(x,3)-0.5*pow(x,2); + } + } + return cubicTable; +} + +float* DivFilterTables:: getSincTable() { + if (sincTable==NULL) { + logD("initializing sinc table.\n"); + sincTable=new float[65536]; + + sincTable[0]=1.0f; + for (int i=1; i<65536; i++) { + int mapped=((i&8191)<<3)|(i>>13); + double x=(double)i*M_PI/8192.0; + sincTable[mapped]=sin(x)/x; + } + + for (int i=0; i<65536; i++) { + int mapped=((i&8191)<<3)|(i>>13); + sincTable[mapped]*=pow(cos(M_PI*(double)i/131072.0),2.0); + } + } + return sincTable; +} + +float* DivFilterTables::getSincIntegralTable() { + if (sincIntegralTable==NULL) { + logD("initializing sinc integral table.\n"); + sincIntegralTable=new float[65536]; + + sincIntegralTable[0]=-0.5f; + for (int i=1; i<65536; i++) { + int mapped=((i&8191)<<3)|(i>>13); + int mappedPrev=(((i-1)&8191)<<3)|((i-1)>>13); + double x=(double)i*M_PI/8192.0; + double sinc=sin(x)/x; + sincIntegralTable[mapped]=sincIntegralTable[mappedPrev]+(sinc/8192.0); + } + + for (int i=0; i<65536; i++) { + int mapped=((i&8191)<<3)|(i>>13); + sincIntegralTable[mapped]*=pow(cos(M_PI*(double)i/131072.0),2.0); + } + } + return sincIntegralTable; +} \ No newline at end of file diff --git a/src/engine/filter.h b/src/engine/filter.h new file mode 100644 index 000000000..974a448e3 --- /dev/null +++ b/src/engine/filter.h @@ -0,0 +1,43 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 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. + */ + +class DivFilterTables { + public: + static float* cubicTable; + static float* sincTable; + static float* sincIntegralTable; + + /** + * get a 1024x4 cubic spline table. + * @return the table. + */ + static float* getCubicTable(); + + /** + * get a 8192x8 one-side sine-windowed sinc table. + * @return the table. + */ + static float* getSincTable(); + + /** + * get a 8192x8 one-side sine-windowed sinc integral table. + * @return the table. + */ + static float* getSincIntegralTable(); +}; \ No newline at end of file diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 53fa42fe2..8b0c78e56 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -19,9 +19,10 @@ #include "sample.h" #include "../ta-log.h" +#include #include #include -#include +#include "filter.h" extern "C" { #include "../../extern/adpcm/bs_codec.h" @@ -178,6 +179,348 @@ bool DivSample::resize(unsigned int count) { return false; } +#define RESAMPLE_BEGIN \ + if (samples<1) return true; \ + int finalCount=(double)samples*(r/(double)rate); \ + signed char* oldData8=data8; \ + short* oldData16=data16; \ + if (depth==16) { \ + if (data16!=NULL) { \ + data16=NULL; \ + initInternal(16,finalCount); \ + } \ + } else if (depth==8) { \ + if (data8!=NULL) { \ + data8=NULL; \ + initInternal(8,finalCount); \ + } \ + } else { \ + return false; \ + } + +#define RESAMPLE_END \ + samples=finalCount; \ + if (depth==16) { \ + delete[] oldData16; \ + } else if (depth==8) { \ + delete[] oldData8; \ + } + +bool DivSample::resampleNone(double r) { + RESAMPLE_BEGIN; + + if (depth==16) { + for (int i=0; i=samples) { + data16[i]=0; + } else { + data16[i]=oldData16[pos]; + } + } + } else if (depth==8) { + for (int i=0; i=samples) { + data8[i]=0; + } else { + data8[i]=oldData8[pos]; + } + } + } + + rate=r; + + RESAMPLE_END; + return true; +} + +bool DivSample::resampleLinear(double r) { + RESAMPLE_BEGIN; + + double posFrac=0; + unsigned int posInt=0; + double factor=(double)rate/r; + + if (depth==16) { + for (int i=0; i=samples)?0:oldData16[posInt]; + short s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData16[loopStart]:0):oldData16[posInt+1]; + + data16[i]=s1+(float)(s2-s1)*posFrac; + + posFrac+=factor; + while (posFrac>=1.0) { + posFrac-=1.0; + posInt++; + } + } + } else if (depth==8) { + for (int i=0; i=samples)?0:oldData8[posInt]; + short s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData8[loopStart]:0):oldData8[posInt+1]; + + data8[i]=s1+(float)(s2-s1)*posFrac; + + posFrac+=factor; + while (posFrac>=1.0) { + posFrac-=1.0; + posInt++; + } + } + } + + rate=r; + + RESAMPLE_END; + return true; +} + +bool DivSample::resampleCubic(double r) { + RESAMPLE_BEGIN; + + double posFrac=0; + unsigned int posInt=0; + double factor=(double)rate/r; + float* cubicTable=DivFilterTables::getCubicTable(); + + if (depth==16) { + for (int i=0; i=samples)?0:oldData16[posInt]; + float s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData16[loopStart]:0):oldData16[posInt+1]; + float s3=(posInt+2>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData16[loopStart]:0):oldData16[posInt+2]; + + float result=s0*t[0]+s1*t[1]+s2*t[2]+s3*t[3]; + if (result<-32768) result=-32768; + if (result>32767) result=32767; + data16[i]=result; + + posFrac+=factor; + while (posFrac>=1.0) { + posFrac-=1.0; + posInt++; + } + } + } else if (depth==8) { + for (int i=0; i=samples)?0:oldData8[posInt]; + float s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData8[loopStart]:0):oldData8[posInt+1]; + float s3=(posInt+2>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData8[loopStart]:0):oldData8[posInt+2]; + + float result=s0*t[0]+s1*t[1]+s2*t[2]+s3*t[3]; + if (result<-128) result=-128; + if (result>127) result=127; + data8[i]=result; + + posFrac+=factor; + while (posFrac>=1.0) { + posFrac-=1.0; + posInt++; + } + } + } + + rate=r; + + RESAMPLE_END; + return true; +} + +bool DivSample::resampleBlep(double r) { + RESAMPLE_BEGIN; + + double posFrac=0; + unsigned int posInt=0; + double factor=r/(double)rate; + float* sincITable=DivFilterTables::getSincIntegralTable(); + float s[16]; + + memset(s,0,16*sizeof(float)); + + if (depth==16) { + memset(data16,0,finalCount*sizeof(short)); + for (int i=0; i32767) result=32767; + data16[i]=result; + } + + posFrac+=1.0; + while (posFrac>=1.0) { + unsigned int n=((unsigned int)(posFrac*8192.0))&8191; + posFrac-=factor; + posInt++; + + float* t1=&sincITable[(8191-n)<<3]; + float* t2=&sincITable[n<<3]; + float delta=oldData16[posInt]-oldData16[posInt-1]; + + for (int j=0; j<8; j++) { + if (i-j>0) { + float result=data16[i-j]+t1[j]*-delta; + if (result<-32768) result=-32768; + if (result>32767) result=32767; + data16[i-j]=result; + } + if (i+j+132767) result=32767; + data16[i+j+1]=result; + } + } + } + } + } else if (depth==8) { + memset(data8,0,finalCount*sizeof(short)); + for (int i=0; i127) result=127; + data8[i]=result; + } + + posFrac+=1.0; + while (posFrac>=1.0) { + unsigned int n=((unsigned int)(posFrac*8192.0))&8191; + posFrac-=factor; + posInt++; + + float* t1=&sincITable[(8191-n)<<3]; + float* t2=&sincITable[n<<3]; + float delta=oldData8[posInt]-oldData8[posInt-1]; + + for (int j=0; j<8; j++) { + if (i-j>0) { + float result=data8[i-j]+t1[j]*-delta; + if (result<-128) result=-128; + if (result>127) result=127; + data8[i-j]=result; + } + if (i+j+1127) result=127; + data8[i+j+1]=result; + } + } + } + } + } + + rate=r; + + RESAMPLE_END; + return true; +} + +bool DivSample::resampleSinc(double r) { + RESAMPLE_BEGIN; + + double posFrac=0; + unsigned int posInt=0; + double factor=(double)rate/r; + float* sincTable=DivFilterTables::getSincTable(); + float s[16]; + + memset(s,0,16*sizeof(float)); + + if (depth==16) { + for (int i=0; i32767) result=32767; + if (i>=8) { + data16[i-8]=result; + } + + posFrac+=factor; + while (posFrac>=1.0) { + posFrac-=1.0; + posInt++; + + for (int j=0; j<15; j++) s[j]=s[j+1]; + s[15]=(posInt>=samples)?0:oldData16[posInt]; + } + } + } else if (depth==8) { + for (int i=0; i32767) result=32767; + if (i>=8) { + data8[i-8]=result; + } + + posFrac+=factor; + while (posFrac>=1.0) { + posFrac-=1.0; + posInt++; + + for (int j=0; j<15; j++) s[j]=s[j+1]; + s[15]=(posInt>=samples)?0:oldData8[posInt]; + } + } + } + + rate=r; + + RESAMPLE_END; + return true; +} + +bool DivSample::resample(double r, int filter) { + if (depth!=8 && depth!=16) return false; + switch (filter) { + case DIV_RESAMPLE_NONE: + return resampleNone(r); + break; + case DIV_RESAMPLE_LINEAR: + return resampleLinear(r); + break; + case DIV_RESAMPLE_CUBIC: + return resampleCubic(r); + break; + case DIV_RESAMPLE_BLEP: + return resampleBlep(r); + break; + case DIV_RESAMPLE_SINC: + return resampleSinc(r); + break; + case DIV_RESAMPLE_BEST: + if (r>rate) { + return resampleSinc(r); + } else { + return resampleBlep(r); + } + break; + } + return false; +} + void DivSample::render() { // step 1: convert to 16-bit if needed if (depth!=16) { diff --git a/src/engine/sample.h b/src/engine/sample.h index 026ca290e..4737a00d8 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -19,6 +19,15 @@ #include "../ta-utils.h" +enum DivResampleFilters { + DIV_RESAMPLE_NONE=0, + DIV_RESAMPLE_LINEAR, + DIV_RESAMPLE_CUBIC, + DIV_RESAMPLE_BLEP, + DIV_RESAMPLE_SINC, + DIV_RESAMPLE_BEST +}; + struct DivSample { String name; int rate, centerRate, loopStart, loopOffP; @@ -53,6 +62,15 @@ struct DivSample { unsigned int samples; + /** + * @warning DO NOT USE - internal functions + */ + bool resampleNone(double rate); + bool resampleLinear(double rate); + bool resampleCubic(double rate); + bool resampleBlep(double rate); + bool resampleSinc(double rate); + /** * save this sample to a file. * @param path a path. @@ -84,6 +102,15 @@ struct DivSample { */ bool resize(unsigned int count); + /** + * change the sample rate. + * @warning do not attempt to resample outside of a synchronized block! + * @param rate number of samples. + * @param filter the interpolation filter. + * @return whether it was successful. + */ + bool resample(double rate, int filter); + /** * initialize the rest of sample formats for this sample. */ diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 07afe23ff..5f38706b1 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -159,7 +159,9 @@ void FurnaceGUI::drawSampleEdit() { } if (ImGui::Button("Resize")) { e->synchronized([this,sample]() { - sample->resize(resizeSize); + if (!sample->resize(resizeSize)) { + showError("couldn't resize! make sure your sample is 8 or 16-bit."); + } e->renderSamples(); }); updateSampleTex=true; @@ -202,7 +204,16 @@ void FurnaceGUI::drawSampleEdit() { if (resampleTarget>96000) resampleTarget=96000; } ImGui::Combo("Filter",&resampleStrat,resampleStrats,6); - ImGui::Button("Resample"); + if (ImGui::Button("Resample")) { + e->synchronized([this,sample]() { + if (!sample->resample(resampleTarget,resampleStrat)) { + showError("couldn't resample! make sure your sample is 8 or 16-bit."); + } + e->renderSamples(); + }); + updateSampleTex=true; + ImGui::CloseCurrentPopup(); + } ImGui::EndPopup(); } else { resampleTarget=sample->rate; @@ -332,7 +343,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::ImageButton(sampleTex,avail,ImVec2(0,0),ImVec2(1,1),0); if (ImGui::IsItemClicked()) { - printf("drawing\n"); + logD("drawing\n"); } ImGui::Text("A workaround! Pretty cool huh?");