├── 1234.wav ├── README.md ├── demo.cpp ├── license.txt ├── makefile ├── resampler.cpp └── resampler.h /1234.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stenzel/newpathdown/87f4e862806a4f76da5a0836abe4e0578a0450e8/1234.wav -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A New Path Down 2 | Maybe a new approach to resampling 3 | 4 | This is resampling with a little twist. I honestly don't know if this idea is new, but I could not find any prior implementation. After discovering this method, I figured out I have been doing it wrong for decades, so with the risk of publishing something that is already common knowledge and making a fool of myself, here some lengthy explanation: 5 | 6 | ## Resampling 7 | Resampling is required whenever we want to play back sampled audio with transposition. 8 | Luckily, sampling is an exact representation of band limited audio, so the continuous signal can be perfectly reconstructed from the samples. 9 | The bad news though is that this requires an infinite summation, which is a bit impractical in most cases. 10 | 11 | For resampling there are basically two versions: 12 | Upsampling is resampling with a higher rate. Pitch goes down if you play back audio upsampled - confusing, I know. Downsampling is resampling with a lower rate, and pitch goes up. 13 | 14 | ## Upsampling 15 | Here the best would be to evaluate the infinite summation formula for each time t to get the resampled sample. To avoid infinite summation, we use a windowed sinc filter of finite length. We'll need this filter sampled at different fractional positions in between samples, so the usual approach is to have a bank of say 256 filters and interpolate between them. Or just take the nearest, this works too. As the bandwidth is increased, there is no need to fight aliasing. This method is quite standard, and I used it for upsampling and downsampling. But no more! 16 | 17 | ## Downsampling 18 | Downsampling has different requirements, the signal has to be bandlimited to half the new sampling rate, which is lower now. Luckily we can combine the polyphase reconstruction filter with a sinc style lowpass, but the lowpass cutoff frequency depends on the downsampling ratio. Lower cutoff requires a longer filter, as a rule of thumb each input sample should contribute to at least two output samples, better more, so the filter length needs to be minimum twice the downsampling ratio. With the filter size and cutoff changing, we also need to have as many samples in direct access as the filter length, which is a limiting factor for many applications where samples are streamed. There is however a simple solution to all these problems of downsampling, so simple that in hindsight I cannot grasp how I could ever be so stupid to miss it. 19 | 20 | ## FIR Filtering 21 | For normal FIR filtering, each output sample y[n] is a linear combination of N input samples x[n-N/2...n+N/2-1] . But that does not mean we have to calculate it that way. What if we take each input sample x[n] and add the contribution to the output samples y[n-N/2...n+N/2-1] ? This works just as well, the contribution from x to y is just the filter in reverse order. If we implement this, we need a buffer of output samples, not input samples. Every step, one output sample is flushed out and a zero is shifted in. (please don't really shift a buffer, it hurts my eyes every time I see such code!) 22 | 23 | ## The New Method 24 | If this scheme is used for downsampling, something strange happens - filter length and cutoff frequency are magically adjusted, and there is no need to increase buffer size with higher downsampling ratios. Calculation will take more time for large downsampling ratios, but that's what you would expect from a longer filter. As input and output sample rates differ, the algorithm is a bit different: 25 | The contribution of x[n] to the N buffered outputs y[0...N] is added. Take the time index m = n/decimation_ratio in the grid of the target rate and use the fractional part to to get the proper polyphase filter. If the integer part of m changes, output a sample and shift in a zero. All the troubles with downsampling disperse! 26 | Although up- and downsampling also share the same polyphase filter bank, the resulting filter for downsampling gets longer with higher ratio. 27 | 28 | However, this cannot be used for upsampling. For a flexible system with time varying resampling ratios, like every sample player, it is mandatory to support up- and downsampling, so if these cannot be combined, it would make little sense. Luckily such a combination is possible, but it requires some special code for the transitions. 29 | 30 | The code here demonstrates this combination of conventional upsampling, the new downsampling and transitions from either to the other. If none of this writing makes sense to you, maybe the code will. It should however by no mean be considered production quality code, in the current form it is more a proof of concept. 31 | 32 | All of this is quite suitable for SIMD optimizations, code for this will appear here in the near future. 33 | 34 | ## Demo 35 | To run the demo, download the files and type "make" in your terminal. A new audio file called "demo.wav" will be created to demonstrate the resampling. The executable file "demo" can also be run with another wav audio file as argument, like "./demo mysong.wav" 36 | -------------------------------------------------------------------------------- /demo.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Demonstration for resampling 3 | (c) 2019 Stefan Stenzel 4 | 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "resampler.h" 12 | 13 | #define MAXSMP 0x100000 14 | float Src[MAXSMP*8]; 15 | 16 | #define NSMP 0x200000 17 | float Out[NSMP]; 18 | 19 | int wavload(const char *filename, float *dst, int maxsmp); 20 | void wavsave(const float *smp, int nsmp, int rate, const char *filename); 21 | 22 | const int Seq[17] = {0,0,0,0,4,7,11, 9, 7, 4, 0,2,5,9,12,11,11}; 23 | 24 | int main(int argc, char *argv[]) 25 | { 26 | resampler resample; 27 | 28 | const char *sample = "1234.wav"; 29 | if(argc >= 2) sample = argv[1]; 30 | 31 | int nsmp = wavload(sample,Src,MAXSMP); 32 | if(nsmp <= 0) return nsmp; 33 | 34 | //loop eight times - ridiculous, I know. 35 | for(int i=1; i<8; i++) memcpy(Src + nsmp * i,Src,nsmp * sizeof(float)); 36 | 37 | float *loopend = Src + nsmp; 38 | 39 | float semitone = 0.0f; 40 | const float *src = Src; 41 | for(int i=0; i>8); 51 | if(index < 17) 52 | { 53 | resample.transpose(semitone + Seq[index]); 54 | } 55 | else 56 | { 57 | semitone -= 0x4p-11f; 58 | resample.transpose(semitone + 11.0f); 59 | } 60 | } 61 | src = resample.process16(src,Out+i); 62 | while(src > loopend) src -= nsmp; 63 | } 64 | printf("saving output to file \"demo.wav\"\n"); 65 | wavsave(Out,NSMP,44100,"demo.wav"); 66 | return 0; 67 | } 68 | 69 | // quick & dirty mono wav file writer 70 | void wavsave(const float *smp, int n, int rate, const char *filename) 71 | { 72 | int head[] = {'FFIR',4*n+36,'EVAW',' tmf',16,0x10003,rate,4*rate,0x200004,'atad',4*n}; 73 | FILE *f = fopen(filename,"wb"); 74 | fwrite(&head,44,1,f); 75 | fwrite(smp,n,4,f); 76 | fclose(f); 77 | } 78 | 79 | // quick & very dirty mono wav file reader 80 | int wavload(const char *filename, float *dst, int maxsmp) 81 | { 82 | FILE *f = fopen(filename,"rb"); 83 | if(!f) return -1; 84 | 85 | uint32_t riff[3]; 86 | fread(riff,4,3,f); 87 | if(riff[0] != 'FFIR' || riff[2] != 'EVAW') {fclose(f); return -2;} 88 | 89 | char *w = (char *) malloc(riff[1]); 90 | char *end = w + fread(w,1,riff[1],f); 91 | fclose(f); 92 | 93 | char *chunk = w; 94 | uint32_t *data = 0,*fmt = 0,*cmp = (uint32_t *)chunk; 95 | 96 | do // get fmt and data chunk 97 | { 98 | if(cmp[0] == ' tmf') fmt = cmp; 99 | if(cmp[0] == 'atad') data = cmp; 100 | chunk += cmp[1] + 8; 101 | cmp = (uint32_t *)chunk; 102 | } while(chunk < end && (!data || !fmt)); 103 | 104 | if(!data || !fmt) {free(w); return -3;} 105 | 106 | uint16_t *wavhead = (uint16_t *) &fmt[2]; 107 | int nsmp = data[1] / wavhead[6]; 108 | if(nsmp > maxsmp) nsmp = maxsmp; 109 | int nchnl = wavhead[1]; 110 | 111 | switch(wavhead[7]) // read first channel only, different formats supported 112 | { 113 | case 64: 114 | { double *src = (double *) &data[2]; 115 | for(int i=0; i 11 | #include 12 | 13 | #include "resampler.h" 14 | 15 | //set transposition in semitones 16 | void resampler::transpose(float semitone) 17 | { 18 | setratio(exp2f(semitone / 12.0f)); 19 | } 20 | 21 | //set resampling ratio 22 | void resampler::setratio(float newratio) 23 | { 24 | ratio = newratio; 25 | downscale = 1.0f / ratio; 26 | deltaup = 0x1p25f * ratio; 27 | deltadown = 0x1p25f / ratio; 28 | 29 | if(deltadown > 0x2000000 && state == DOWN) state = DOWN2UP; 30 | if(deltaup > 0x2000000 && state == UP) state = UP2DOWN; 31 | } 32 | 33 | // conventional approach, used for upsampling 34 | const float *resampler::up16(const float *src, float *dst) 35 | { 36 | uint32_t ixsmp = (counter >> 25) & 0x07; 37 | for(int i=0; i<16; i++) 38 | { 39 | counter += deltaup; 40 | uint32_t ixnew = (counter >> 25) & 0x07; 41 | 42 | //if(ixnew != ixsmp) in[ixsmp] = *src++; 43 | while(ixnew != ixsmp) // this allows this routine to be uses for slight upsampling as well. 44 | { 45 | in[ixsmp] = *src++; 46 | ixsmp = (ixsmp + 1) & 0x07; 47 | } 48 | 49 | uint32_t ixflt = (counter >> 17) & 0x0FF; 50 | float fraction = (counter & 0x1FFFF) * 0x1p-17f; 51 | float *sinca = &Sinc[ixflt][0]; 52 | float *sincb = &Sinc[ixflt+1][0]; 53 | 54 | float acca = 0.0f; 55 | float accb = 0.0f; 56 | for(int i=0; i<8; i++) 57 | { 58 | acca += in[ixsmp] * *sinca++; 59 | accb += in[ixsmp] * *sincb++; 60 | ixsmp = (ixsmp + 1) & 0x07; 61 | } 62 | *dst++ = acca + (accb - acca) * fraction; 63 | } 64 | return src; 65 | } 66 | 67 | //the new method for downsampling 68 | const float *resampler::down16(const float *src, float *dst) 69 | { 70 | uint32_t ixsmp = (counter >> 25) & 0x07; 71 | float *end = dst + 16; 72 | do { 73 | uint32_t ixflt = (counter >> 17) & 0x0FF; 74 | float fraction = (counter & 0x1FFFF) * 0x1p-17f; 75 | float *sinca = &Sinc[ixflt][0]; 76 | float *sincb = &Sinc[ixflt+1][0]; 77 | float x = *src++ * downscale; 78 | 79 | float xb = x * fraction; 80 | float xa = x - xb; 81 | 82 | for(int i=0; i<8; i++) 83 | { 84 | out[ixsmp] += xa * *sinca++; 85 | out[ixsmp] += xb * *sincb++; 86 | ixsmp = (ixsmp + 1) & 0x07; 87 | } 88 | counter += deltadown; 89 | uint32_t ixnew = (counter >> 25) & 0x07; 90 | if(ixnew != ixsmp) 91 | { 92 | *dst++ = out[ixsmp]; 93 | out[ixsmp] = 0.0f; 94 | ixsmp = ixnew; 95 | } 96 | } while(dst < end); 97 | return src; 98 | } 99 | 100 | // used for fading in new version oer eight samples 101 | // cannot name things "new", so I used German 102 | void fade(float *dst, float *alt, float *neu) 103 | { 104 | for(int i=0; i<8; i++) *dst++ = *alt++; 105 | 106 | neu += 8; 107 | 108 | const float step = 1.0f/9.0f; 109 | float sc = step; 110 | for(int i=0; i<8; i++) 111 | { 112 | float a = *alt++; 113 | *dst++ = a + (*neu++ - a) * sc; 114 | sc += step; 115 | } 116 | } 117 | 118 | //transition from downsampling to upsampling 119 | const float *resampler::down2up16(const float *src, float *dst) 120 | { 121 | float neu[16]; 122 | uint32_t nextcount = ~counter; 123 | down16(src,dst); 124 | state = UP; 125 | counter = nextcount; 126 | for(int i=0; i<8; i++) in[i] = 0.0f; 127 | src = up16(src,neu); 128 | fade(dst,dst,neu); 129 | return src; 130 | } 131 | 132 | //transition from upsampling to downsampling 133 | const float *resampler::up2down16(const float *src, float *dst) 134 | { 135 | float neu[16]; 136 | uint32_t nextcount = ~counter; 137 | up16(src,dst); 138 | state = DOWN; 139 | counter = nextcount; 140 | for(int i=0; i<8; i++) out[i] = 0.0f; 141 | src = down16(src,neu); 142 | fade(dst,dst,neu); 143 | return src; 144 | } 145 | 146 | //process a block of 16 samples 147 | //eats a variable number of samples 148 | //returns pointer behind the last eaten sample 149 | const float *resampler::process16(const float *src, float *dst) 150 | { 151 | switch(state) 152 | { 153 | case UP: return up16(src,dst); 154 | case DOWN: return down16(src,dst); 155 | case UP2DOWN: return up2down16(src,dst); 156 | case DOWN2UP: 157 | default: return down2up16(src,dst); 158 | } 159 | } 160 | 161 | #define WCF0 0.344109006115 162 | #define WCF1 -0.508650393885 163 | #define WCF2 0.193119398005 164 | #define WCF3 -0.028578010236 165 | #define DILATION 0.83 166 | 167 | //build the windowed sinc filter table 168 | void resampler::buildsinc(void) 169 | { 170 | double step = M_PI * DILATION; 171 | double wstep = M_PI * 2.0/(8 + 1.0); 172 | 173 | for(int i=0; i<=256; i++) 174 | { 175 | double fraction = 1.0f - i * 1.0/256; 176 | double arg = (fraction - 8 * 0.5) * step; 177 | double warg = (fraction + 0.5) * wstep; 178 | 179 | for(int k=0; k<8; k++) 180 | { 181 | double s = 1.0; 182 | if(arg) s = sin(arg)/arg; // sinc - but avoid division by zero. 183 | 184 | // Apply the Stenzel Window to minimize DC error 185 | double w = WCF0 + WCF1 * cos(warg) + WCF2 * cos(warg*2.0) + WCF3 * cos(warg*3.0); 186 | 187 | Sinc[i][k] = s * w; // save windowed tap 188 | 189 | arg += step; // increment sinc arg 190 | warg += wstep; // increment window arg 191 | } 192 | } 193 | } 194 | 195 | float resampler::Sinc[256+1][8]; 196 | 197 | //constructor... 198 | resampler::resampler() 199 | { 200 | counter = 0; 201 | state = UP; 202 | setratio(1.0f); 203 | for(int i=0; i<8; i++) 204 | { 205 | in[i] = 0; 206 | out[i] = 0; 207 | } 208 | buildsinc(); 209 | } 210 | 211 | -------------------------------------------------------------------------------- /resampler.h: -------------------------------------------------------------------------------- 1 | /* 2 | Resampling using different methods for up- and downsampling 3 | (c) 2019 Stefan Stenzel 4 | stefan at ioptigan dot com 5 | 6 | See license.txt for terms of use 7 | 8 | */ 9 | #pragma once 10 | 11 | #include 12 | 13 | class resampler 14 | { 15 | float ratio; 16 | float downscale; 17 | uint32_t counter; 18 | uint32_t deltaup; 19 | uint32_t deltadown; 20 | 21 | enum {UP,DOWN,UP2DOWN,DOWN2UP}; 22 | int state; 23 | float in[8]; 24 | float out[8]; 25 | 26 | static float Sinc[256+1][8]; 27 | void buildsinc(void); 28 | 29 | const float *down16(const float *src, float *dst); 30 | const float *up16(const float *src, float *dst); 31 | const float *down2up16(const float *src, float *dst); 32 | const float *up2down16(const float *src, float *dst); 33 | public: 34 | resampler(); 35 | void setratio(float newratio); 36 | void transpose(float semitone); 37 | const float *process16(const float *src, float *dst); 38 | }; 39 | --------------------------------------------------------------------------------