1 /** 2 * Copyright: Copyright Auburn Sounds 2015 and later. 3 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 4 * Authors: Guillaume Piolat 5 */ 6 module dplug.dsp.fir; 7 8 import std.range, 9 std.math, 10 std.complex; 11 12 import dplug.core, 13 dplug.dsp.fft, 14 dplug.dsp.delayline, 15 dplug.dsp.window; 16 17 // FUTURE: 18 // - bandstop/bandstop IR 19 // - naive convolution 20 21 // sinc impulse functions 22 23 void generateLowpassImpulse(T)(T[] output, double cutoff, double samplerate) nothrow @nogc 24 { 25 checkFilterParams(output.length, cutoff, samplerate); 26 double cutoffNormalized = cutoff / samplerate; 27 double wc = cutoffNormalized * 2.0 * PI; 28 29 int len = cast(int)(output.length); 30 for (int i = 0; i < len; ++i) 31 { 32 int x = i - (len / 2); 33 if (x == 0) 34 output[i] = wc; 35 else 36 output[i] = sin(wc * x) / cast(double)x; 37 } 38 normalizeImpulse(output); 39 } 40 41 void generateHighpassImpulse(T)(T[] output, double cutoff, double samplerate) nothrow @nogc 42 { 43 checkFilterParams(output.length, cutoff, samplerate); 44 double cutoffNormalized = cutoff / samplerate; 45 double wc = cutoffNormalized * 2.0 * PI; 46 47 int len = cast(int)(output.length); 48 for (int i = 0; i < len; ++i) 49 { 50 int x = i - (len / 2); 51 if (x == 0) 52 output[i] = 1 - wc; 53 else 54 output[i] = -sin(wc * x) / cast(double)x; 55 } 56 normalizeImpulse(output); 57 } 58 59 void generateHilbertTransformer(T)(T[] outImpulse, WindowDesc windowDesc, double samplerate) nothrow @nogc 60 { 61 int size = cast(int)(outImpulse.length); 62 assert(isOdd(size)); 63 int center = size / 2; 64 65 for (int i = 0; i < center; ++i) 66 { 67 int xi = i - center; 68 double x = cast(double)xi; 69 if (isEven(xi)) 70 { 71 outImpulse[i] = 0; 72 outImpulse[size - 1 - i] = 0; 73 } 74 else 75 { 76 double y = x * cast(double)PI / 2; 77 double sine = sin(y); 78 T value = cast(T)(-sine*sine / y); 79 value *= evalWindow(windowDesc, i, size); 80 outImpulse[i] = value; 81 outImpulse[size - 1 - i] = -value; 82 } 83 } 84 outImpulse[center] = 0; 85 } 86 87 88 /// Normalize impulse response. 89 /// Scale to make sum = 1. 90 void normalizeImpulse(T)(T[] inoutImpulse) nothrow @nogc 91 { 92 int size = cast(int)(inoutImpulse.length); 93 double sum = 0; 94 for (int i = 0; i < size; ++i) 95 sum += inoutImpulse[i]; 96 97 double invSum = 1 / sum; 98 for (int i = 0; i < size; ++i) 99 inoutImpulse[i] = cast(T)(inoutImpulse[i] * invSum); 100 } 101 102 /// Returns: Length of temporary buffer needed for `minimumPhaseImpulse`. 103 int tempBufferSizeForMinPhase(T)(T[] inputImpulse) nothrow @nogc 104 { 105 return cast(int)( nextPowerOf2(inputImpulse.length) ); 106 } 107 108 /// From an impulse, computes a minimum-phase impulse 109 /// Courtesy of kasaudio, based on Aleksey Vaneev's algorithm 110 /// See: http://www.kvraudio.com/forum/viewtopic.php?t=197881 111 /// MAYDO: does it preserve amplitude? 112 void minimumPhaseImpulse(T)(T[] inoutImpulse, Complex!T[] tempStorage) nothrow @nogc // alloc free version 113 { 114 assert(tempStorage.length >= tempBufferSizeForMinPhase(inoutImpulse)); 115 116 int N = cast(int)(inoutImpulse.length); 117 int fftSize = cast(int)( nextPowerOf2(inoutImpulse.length) ); 118 assert(fftSize >= N); 119 int halfFFTSize = fftSize / 2; 120 121 if (tempStorage.length < fftSize) 122 assert(false); // crash 123 124 auto kernel = tempStorage; 125 126 for (int i = 0; i < N; ++i) 127 kernel[i] = inoutImpulse[i]; 128 129 for (int i = N; i < fftSize; ++i) 130 kernel[i] = 0; 131 132 forwardFFT!T(kernel[]); 133 134 for (int i = 0; i < fftSize; ++i) 135 kernel[i] = log(abs(kernel[i])); 136 137 inverseFFT!T(kernel[]); 138 139 for (int i = 1; i < halfFFTSize; ++i) 140 kernel[i] *= 2; 141 142 for (int i = halfFFTSize + 1; i < halfFFTSize; ++i) 143 kernel[i] = 0; 144 145 forwardFFT!T(kernel[]); 146 147 for (int i = 0; i < fftSize; ++i) 148 kernel[i] = complexExp(kernel[i]); 149 150 inverseFFT!T(kernel[]); 151 152 for (int i = 0; i < N; ++i) 153 inoutImpulse[i] = kernel[i].re; 154 } 155 156 private Complex!T complexExp(T)(Complex!T z) nothrow @nogc 157 { 158 T mag = exp(z.re); 159 return Complex!T(mag * cos(z.re), mag * sin(z.im)); 160 } 161 162 163 private static void checkFilterParams(size_t length, double cutoff, double sampleRate) nothrow @nogc 164 { 165 assert((length & 1) == 0, "FIR impulse length must be even"); 166 assert(cutoff * 2 < sampleRate, "2x the cutoff exceed sampling rate, Nyquist disapproving"); 167 } 168 169 170 unittest 171 { 172 double[256] lp_impulse; 173 double[256] hp_impulse; 174 175 generateLowpassImpulse(lp_impulse[], 40.0, 44100.0); 176 generateHighpassImpulse(hp_impulse[], 40.0, 44100.0); 177 178 Complex!double[] tempStorage = new Complex!double[tempBufferSizeForMinPhase(lp_impulse[])]; 179 minimumPhaseImpulse(lp_impulse[], tempStorage); 180 181 generateHilbertTransformer(lp_impulse[0..$-1], WindowDesc(WindowType.BLACKMANN_HARRIS), 44100.0); 182 } 183 184 185 // Composed of a delay-line, and an inpulse. 186 struct FIR(T) 187 { 188 /// Initializes the FIR filter. It's up to you to fill the impulse with something worthwhile. 189 void initialize(int sizeOfImpulse) nothrow @nogc 190 { 191 assert(sizeOfImpulse > 0); 192 _delayline.initialize(sizeOfImpulse); 193 _impulse.reallocBuffer(sizeOfImpulse); 194 _impulse[] = T.nan; 195 } 196 197 ~this() nothrow @nogc 198 { 199 _impulse.reallocBuffer(0); 200 _tempBuffer.reallocBuffer(0); 201 _windowBuffer.reallocBuffer(0); 202 } 203 204 @disable this(this); 205 206 /// Returns: Filtered input sample, naive convolution. 207 T nextSample(T input) nothrow @nogc 208 { 209 _delayline.feedSample(input); 210 T sum = 0; 211 int N = cast(int)impulse.length; 212 for (int i = 0; i < N; ++i) 213 sum += _impulse.ptr[i] * _delayline.sampleFull(i); 214 return sum; 215 } 216 217 /// Returns: Impulse response. If you write it, you can call makeMinimumPhase() next. 218 inout(T)[] impulse() inout nothrow @nogc 219 { 220 return _impulse; 221 } 222 223 void makeMinimumPhase() nothrow @nogc 224 { 225 int sizeOfTemp = tempBufferSizeForMinPhase(_impulse); 226 _tempBuffer.reallocBuffer(sizeOfTemp); 227 minimumPhaseImpulse!T(_impulse, _tempBuffer); 228 } 229 230 void applyWindow(WindowDesc windowDesc) nothrow @nogc 231 { 232 _windowBuffer.reallocBuffer(_impulse.length); 233 generateWindow(windowDesc, _windowBuffer); 234 foreach(i; 0.._impulse.length) 235 { 236 _impulse[i] *= _windowBuffer[i]; 237 } 238 } 239 240 private: 241 T[] _impulse; 242 243 Delayline!T _delayline; 244 Complex!T[] _tempBuffer; 245 T[] _windowBuffer; 246 } 247 248 unittest 249 { 250 FIR!double fir; 251 fir.initialize(32); 252 generateLowpassImpulse(fir.impulse(), 40.0, 44100.0); 253 fir.makeMinimumPhase(); 254 fir.applyWindow(WindowDesc(WindowType.HANN)); 255 }