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