1 /**
2 High-level interfaces for providing FFT analysis, real FFT, and resynthesis from grains.
3 
4 Copyright: Guillaume Piolat 2015.
5 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 */
7 module dplug.fft.fft;
8 
9 import core.stdc.string;
10 
11 import std.math;
12 import std.complex;
13 
14 import dplug.dsp.window;
15 import dplug.core.math;
16 import dplug.core.vec;
17 import dplug.fft;
18 
19 
20 enum FFTDirection
21 {
22     FORWARD = 0,
23     REVERSE = 1
24 }
25 
26 /// Perform in-place FFT.
27 /// Equivalent to `std.numeric.fft`, but this one is nothrow @nogc.
28 public void forwardFFT(T)(Complex!T[] buffer) nothrow @nogc
29 {
30     FFT_internal!(T, FFTDirection.FORWARD)(buffer);
31 }
32 
33 /// Perform in-place inverse FFT.
34 /// Equivalent to `std.numeric.inverseFft`, but this one is nothrow @nogc.
35 public void inverseFFT(T)(Complex!T[] buffer) nothrow @nogc
36 {
37     FFT_internal!(T, FFTDirection.REVERSE)(buffer);
38 }
39 
40 // PERF: use pfft instead would be much faster
41 private void FFT_internal(T, FFTDirection direction)(Complex!T[] buffer) pure nothrow @nogc
42 {
43     static int intFloorLog2(int i) pure nothrow @nogc @safe
44     {
45         assert(i >= 1);
46         int result = 0;
47         while (i > 1)
48         {
49             i = i / 2;
50             result = result + 1;
51         }
52         return result;
53     }
54 
55     int size = cast(int)(buffer.length);
56     assert(isPowerOfTwo(size));
57     int m = intFloorLog2(size);
58 
59     Complex!T* pbuffer = buffer.ptr;
60 
61     // do the bit reversal
62     int i2 = cast(int)size / 2;
63     int j = 0;
64     for (int i = 0; i < size - 1; ++i)
65     {
66         if (i < j)
67         {
68             auto tmp = pbuffer[i];
69             pbuffer[i] = pbuffer[j];
70             pbuffer[j] = tmp;
71         }
72 
73         int k = i2;
74         while(k <= j)
75         {
76             j = j - k;
77             k = k / 2;
78         }
79         j += k;
80     }
81 
82     // compute the FFT
83     Complex!T c = Complex!T(-1, 0);
84     int l2 = 1;
85     for (int l = 0; l < m; ++l)
86     {
87         int l1 = l2;
88         l2 = l2 * 2;
89         Complex!T u = Complex!T(1, 0);
90         for (int j2 = 0; j2 < l1; ++j2)
91         {
92             int i = j2;
93             while (i < size)
94             {
95                 int i1 = i + l1;
96                 Complex!T t1 = u * pbuffer[i1];
97                 pbuffer[i1] = pbuffer[i] - t1;
98                 pbuffer[i] += t1;
99                 i += l2;
100             }
101             u = u * c;
102         }
103 
104         T newImag = sqrt((1 - c.re) / 2);
105         static if (direction == FFTDirection.FORWARD)
106             newImag = -newImag;
107         T newReal = sqrt((1 + c.re) / 2);
108         c = Complex!T(newReal, 1.0f * newImag);
109     }
110 
111     // scaling when doing the reverse transformation, to avoid being multiplied by size
112     static if (direction == FFTDirection.REVERSE)
113     {
114         T divider = 1 / cast(T)size;
115         for (int i = 0; i < size; ++i)
116         {
117             pbuffer[i] = pbuffer[i] * divider;
118         }
119     }
120 }
121 
122 
123 // should operate the same as Phobos FFT
124 unittest
125 {
126     import std.complex;
127     import std.numeric: fft;
128 
129     bool approxEqualArrBuiltin(Complex!double[] a, Complex!double[] b) pure
130     {
131         foreach(i; 0..a.length)
132         {
133             if (!isClose(a[i].re, b[i].re))
134                 return false;
135             if (!isClose(a[i].im, b[i].im))
136                 return false;
137         }
138         return true;
139     }
140 
141     bool approxEqualArr(Complex!double[] a, Complex!double[] b) pure
142     {
143         foreach(i; 0..a.length)
144         {
145             if (!isClose(a[i].re, b[i].re))
146                 return false;
147             if (!isClose(a[i].im, b[i].im))
148                 return false;
149         }
150         return true;
151     }
152 
153     //BuiltinComplex!double[] A = [1+0i, 13-4i, 5-5i, 0+2i];
154     Complex!double[] Abis = [Complex!double(1, 0), Complex!double(13, -4), Complex!double(5,-5), Complex!double(0,2)];
155     Complex!double[] fftARef = fft(Abis);
156 
157     auto B = Abis.dup;
158     forwardFFT!double(B);
159     assert(approxEqualArr(B, fftARef));
160     inverseFFT!double(B);
161     assert(approxEqualArrBuiltin(B, Abis));
162 }
163 
164 /// From a signal, output chunks of determined size, with optional overlap.
165 /// Introduces approximately windowSize/2 samples delay.
166 struct Segmenter(T)
167 {
168 nothrow:
169 @nogc:
170 
171     int segmentSize() pure const
172     {
173         return _segmentSize;
174     }
175 
176     int analysisPeriod() pure const
177     {
178         return _analysisPeriod;
179     }
180 
181     /// To call at initialization and whenever samplerate changes.
182     /// segmentSize = size of sound segments, expressed in samples.
183     /// analysisPeriod = period of analysis results, allow to be more precise frequentially, expressed in samples.
184     void initialize(int segmentSize, int analysisPeriod)
185     {
186         assert(analysisPeriod <= segmentSize); // no support for zero overlap
187 
188         // 1-sized FFT support
189         if (analysisPeriod == 0)
190             analysisPeriod = 1;
191 
192         _segmentSize = segmentSize;
193         _analysisPeriod = analysisPeriod;
194 
195         // clear input delay
196         _buffer.reallocBuffer(_segmentSize);
197         _buffer[] = 0;
198         _index = 0;
199     }
200 
201     ~this()
202     {
203         _buffer.reallocBuffer(0);
204     }
205 
206     @disable this(this);
207 
208     // Push one sample, eventually call the delegate to process a segment.
209     bool feed(T x, scope void delegate(T[] segment) nothrow @nogc processSegment = null)
210     {
211         _buffer[_index] = x;
212         _index = _index + 1;
213         if (_index >= _segmentSize)
214         {
215             // process segment (optional)
216             if (processSegment !is null)
217                 processSegment(_buffer[0.._segmentSize]);
218 
219             // rotate buffer
220             {
221                 int samplesToDrop = _analysisPeriod;
222                 assert(0 < samplesToDrop && samplesToDrop <= _segmentSize);
223                 int remainingSamples = _segmentSize - samplesToDrop;
224 
225                 // FUTURE: use ring buffer instead of copy?
226                 memmove(_buffer.ptr, _buffer.ptr + samplesToDrop, T.sizeof * remainingSamples);
227                 _index = remainingSamples;
228 
229             }
230             return true;
231         }
232         else
233             return false;
234     }
235 
236     /// Returns: Internal buffer.
237     T[] buffer()
238     {
239         return _buffer;
240     }
241 
242 private:
243     T[] _buffer;
244     int _segmentSize;     // in samples
245     int _analysisPeriod; // in samples
246     int _index;
247 }
248 
249 
250 /// From short term windowed data, output the summed signal.
251 /// Segments can be irregular and have different size.
252 struct ShortTermReconstruction(T)
253 {
254 nothrow:
255 @nogc:
256     /// maxSimultSegments is the maximum number of simulatneously summed samples.
257     /// maxSegmentLength in samples
258     void initialize(int maxSimultSegments, int maxSegmentLength)
259     {
260         _maxSegmentLength = maxSegmentLength;
261         _maxSimultSegments = maxSimultSegments;
262 
263         _desc.reallocBuffer(maxSimultSegments);
264         _mergedBuffer.reallocBuffer(_maxSegmentLength * _maxSimultSegments);
265 
266         for (int i = 0; i < _maxSimultSegments; ++i)
267         {
268             _desc[i].playOffset = 0; // initially inactive
269             _desc[i].length = 0;
270             _desc[i].buffer = &_mergedBuffer[maxSegmentLength*i];
271         }
272     }
273 
274     ~this()
275     {
276         _mergedBuffer.reallocBuffer(0);
277         _desc.reallocBuffer(0);
278     }
279 
280     @disable this(this);
281 
282     // Copy segment to a free slot, and start its summing.
283     // The first sample of this segment will be played at next() call if delay is 0.
284     void startSegment(T[] newSegment, int delay = 0)
285     {
286         assert(newSegment.length <= _maxSegmentLength);
287         int i = allocSegmentSlot();
288         int len = cast(int)(newSegment.length);
289         _desc[i].playOffset = -delay;
290         _desc[i].length = len;
291         _desc[i].buffer[0..len] = newSegment[]; // copy segment
292     }
293 
294     // Same, but with the input being split into two slices A ~ B. This is a common case
295     // when summing zero-phase windows in STFT analysis.
296     void startSegmentSplitted(T[] segmentA, T[] segmentB, int delay = 0)
297     {
298         int i = allocSegmentSlot();
299         int lenA = cast(int)(segmentA.length);
300         int lenB = cast(int)(segmentB.length);
301         assert(lenA + lenB <= _maxSegmentLength);
302 
303         _desc[i].playOffset = -delay;
304         _desc[i].length = lenA + lenB;
305         _desc[i].buffer[0..lenA] = segmentA[];         // copy segment part A
306         _desc[i].buffer[lenA..lenA+lenB] = segmentB[]; // copy segment part B
307     }
308 
309     T nextSample()
310     {
311         float sum = 0;
312         foreach(ref desc; _desc)
313         {
314             if (desc.playOffset < desc.length)
315             {
316                 if (desc.playOffset >= 0)
317                     sum += desc.buffer[desc.playOffset];
318                 desc.playOffset += 1;
319             }
320         }
321         return sum;
322     }
323 
324     void nextBuffer(T* outAudio, int frames)
325     {
326         outAudio[0..frames] = 0;
327 
328         // Add each pending segment
329         foreach(ref desc; _desc)
330         {
331             const int offset = desc.playOffset;
332             const int len = desc.length;
333             if (offset < len)
334             {
335                 // Compute relative time event for the segment
336                 int startOfSegment = -offset;
337                 int endOfSegment = startOfSegment + len;
338 
339                 // Compute the area in 0..frames we can playback the segment
340                 int startOfSumming = startOfSegment;
341                 if (startOfSumming < 0)
342                     startOfSumming = 0;
343                 if (startOfSumming >= frames)
344                     startOfSumming = frames;
345                 int endOfSumming = endOfSegment;
346                 if (endOfSumming >= frames)
347                     endOfSumming = frames;
348 
349                 int count = endOfSumming - startOfSumming;
350                 assert(count >= 0);
351 
352                 const(T)* segmentData = desc.buffer + offset;
353 
354                 // PERF: this can be optimized further
355                 for (int i = startOfSumming; i < endOfSumming; ++i)
356                 {
357                     outAudio[i] += segmentData[i];
358                 }
359                 desc.playOffset = offset + frames;
360             }
361             // else disabled segment
362         }
363     }
364 
365 private:
366 
367     struct SegmentDesc
368     {
369         int playOffset; // offset in this segment
370         int length; // length in this segment
371         T* buffer; // 0..length => data for this segment
372 
373         bool active() pure const nothrow @nogc
374         {
375             return playOffset < length;
376         }
377     }
378     int _maxSimultSegments;
379     int _maxSegmentLength;
380     SegmentDesc[] _desc;
381     T[] _mergedBuffer; // Allocation for every segment buffer.
382 
383     int allocSegmentSlot()
384     {
385         for (int i = 0; i < _maxSimultSegments; ++i)
386             if (!_desc[i].active())
387                 return i;
388         assert(false); // maxSimultSegments too small, or usage error
389     }
390 }
391 
392 /// From a signal, output short term FFT data.
393 /// Variable overlap.
394 /// Introduces approximately windowSize/2 samples delay.
395 /// Uses a real FFT to gain some speed.
396 struct FFTAnalyzer(T)
397 {
398 public:
399 
400     /// To call at initialization and whenever samplerate changes.
401     /// windowSize = size of window, expressed in samples
402     /// fftSize = size of FFT. Must be power-of-two and >= windowSize. Missing samples are zero-padded in time domain.
403     /// analysisPeriod = period of analysis results, allow to be more precise frequentially, expressed in samples.
404     /// Basic overlap is achieved with windowSize = 2 * analysisPeriod
405     /// if zeroPhaseWindowing = true, "zero phase" windowing is used
406     /// (center of window is at first sample, zero-padding happen at center)
407     void initialize(int windowSize,
408                     int fftSize,
409                     int analysisPeriod,
410                     WindowDesc windowDesc,
411                     bool zeroPhaseWindowing) nothrow @nogc
412     {
413         assert(isPowerOfTwo(fftSize));
414         assert(fftSize >= windowSize);
415 
416         _zeroPhaseWindowing = zeroPhaseWindowing;
417 
418         _fftSize = fftSize;
419 
420         _window.initialize(windowDesc, windowSize);
421         _windowSize = windowSize;
422 
423         // account for window shape
424         _scaleFactor = fftSize / _window.sumOfWindowSamples();
425 
426         // account for overlap
427         _scaleFactor *= cast(float)(analysisPeriod) / windowSize;
428 
429         _segmenter.initialize(windowSize, analysisPeriod);
430 
431         _timeData.reallocBuffer(fftSize);
432         _rfft.initialize(fftSize);
433     }
434 
435     ~this()
436     {
437         _timeData.reallocBuffer(0);
438     }
439 
440     /// Gets the RFFT object which allows to perform efficient inverse FFT with the same pre-computed tables.
441     ref RFFT!T realFFT()
442     {
443         return _rfft;
444     }
445 
446     bool feed(T x, Complex!T[] fftData) nothrow @nogc
447     {
448         void processSegment(T[] segment) nothrow @nogc
449         {
450             int windowSize = _windowSize;
451             assert(segment.length == _windowSize);
452 
453             T scaleFactor = _scaleFactor;
454 
455             if (_zeroPhaseWindowing)
456             {
457                 // "Zero Phase" windowing
458                 // Through clever reordering, phase of ouput coefficients will relate to the
459                 // center of the window
460                 //_
461                 // \_                   _/
462                 //   \                 /
463                 //    \               /
464                 //     \_____________/____
465                 int center = (_windowSize - 1) / 2; // position of center bin
466                 int nLeft = _windowSize - center;
467                 for (int i = 0; i < nLeft; ++i)
468                     _timeData[i] = (segment[center + i] * _window[center + i] * scaleFactor);
469 
470                 int nPadding = _fftSize - _windowSize;
471                 for (int i = 0; i < nPadding; ++i)
472                     _timeData[nLeft + i] = 0;
473 
474                 for (int i = 0; i < center; ++i)
475                     _timeData[nLeft + nPadding + i] = (segment[i] * _window[i] * scaleFactor);
476             }
477             else
478             {
479                 // "Normal" windowing
480                 // Phase of output coefficient will relate to the start of the buffer
481                 //      _
482                 //    _/ \_
483                 //   /     \
484                 //  /       \
485                 //_/         \____________
486 
487                 // fill FFT buffer and multiply by window
488                 for (int i = 0; i < _windowSize; ++i)
489                     _timeData[i] = (segment[i] * _window[i] * scaleFactor);
490 
491                 // zero-padding
492                 for (int i = _windowSize; i < _fftSize; ++i)
493                     _timeData[i] = 0;
494             }
495 
496             // If you fail here, you are giving a larger slice than strictly necessary to FFTAnalyzer.
497             // This can cause hard to find memory corruption if you read the slice one bin too far.
498             // Give a slice with length of exactly _fftSize/2+1.
499             assert(fftData.length == _fftSize/2+1, "FFTAnalyzer is given too large a slice");
500             _rfft.forwardTransform(_timeData[], fftData[0.._fftSize/2+1]);
501         }
502 
503         return _segmenter.feed(x, &processSegment);
504     }
505 
506 private:
507     Segmenter!T _segmenter;
508     bool _zeroPhaseWindowing;
509     int _fftSize;        // in samples
510 
511     Window!T _window;
512     int _windowSize;     // in samples
513 
514     T _scaleFactor; // account to the shape of the windowing function
515 
516     RFFT!T _rfft;
517     T[] _timeData;
518 }
519 
520 unittest
521 {
522     FFTAnalyzer!float a;
523     a.initialize(1024, 2048, 512, WindowDesc(WindowType.hann, WindowAlignment.left), true);
524 
525     FFTAnalyzer!double b;
526     b.initialize(1024, 2048, 512, WindowDesc(WindowType.hann, WindowAlignment.right), false);
527 }
528 
529 
530 /// Converts a normalized frequency to a FFT bin.
531 /// Params:
532 ///     normalizedFrequency = Frequency in cycles per sample.
533 ///     fftSize = Size of FFT.
534 /// Returns: Corresponding fractional bin.
535 float convertNormalizedFrequencyToFFTBin(float normalizedFrequency, int fftSize) nothrow @nogc
536 {
537     return (normalizedFrequency * fftSize);
538 }
539 
540 /// Converts a frequency to a FFT bin.
541 /// Returns: Corresponding fractional bin.
542 float convertFrequencyToFFTBin(float frequencyHz, float samplingRate, int fftSize) nothrow @nogc
543 {
544     return (frequencyHz * fftSize) / samplingRate;
545 }
546 
547 /// Converts a frequency to a FFT bin.
548 /// Returns: Corresponding fractional bin.
549 float convertFrequencyToFFTBinInv(float frequencyHz, float invSamplingRate, int fftSize) nothrow @nogc
550 {
551     return (frequencyHz * fftSize) * invSamplingRate;
552 }
553 
554 /// Converts a FFT bin to a frequency.
555 /// Returns: Corresponding center frequency.
556 float convertFFTBinToFrequency(float fftBin, int fftSize, float samplingRate) nothrow @nogc
557 {
558     return (samplingRate * fftBin) / fftSize;
559 }
560 
561 /// Converts a FFT bin to a frequency.
562 /// Returns: Corresponding center frequency.
563 float convertFFTBinToFrequencyInv(float fftBin, float invFFTSize, float samplingRate) nothrow @nogc
564 {
565     return (samplingRate * fftBin) * invFFTSize;
566 }
567 
568 /// Converts a FFT bin to a normalized frequency.
569 /// Params:
570 ///     fftBin = Bin index in the FFT.
571 ///     fftSize = Size of FFT.
572 /// Returns: Corresponding normalized frequency
573 float convertFFTBinToNormalizedFrequency(float fftBin, int fftSize) nothrow @nogc
574 {
575     return fftBin / fftSize;
576 }
577 
578 
579 /// Converts a FFT bin to a normalized frequency.
580 /// Params:
581 ///     fftBin = Bin index of the FFT.
582 ///     invFFTSize = Inverse size of FFT.
583 /// Returns: Corresponding normalized frequency.
584 float convertFFTBinToNormalizedFrequencyInv(float fftBin, float invFFTSize) nothrow @nogc
585 {
586     return fftBin * invFFTSize;
587 }
588 
589 /// Perform a FFT from a real signal, saves up CPU.
590 struct RFFT(T)
591 {
592 public:
593 nothrow:
594 @nogc:
595 
596     void initialize(int length)
597     {
598         _length = length;
599         _internal.initialize(length);
600 
601         int newAlignment = cast(int)_internal.alignment(length);
602 
603         // if the alignement changes, we can't reuse that buffer
604         if (_alignment != -1 && _alignment != newAlignment)
605         {
606             _buffer.reallocBuffer(0, _alignment);
607         }
608 
609         _buffer.reallocBuffer(length, newAlignment);
610         _alignment = newAlignment;
611     }
612 
613     ~this()
614     {
615         if (_buffer != null)
616             _buffer.reallocBuffer(0, _alignment);
617     }
618 
619     @disable this(this);
620 
621     void forwardTransform(const(T)[] timeData, Complex!T[] outputBins)
622     {
623         _buffer[] = timeData[];
624 
625         // Perform real FFT
626         _internal.rfft(_buffer);
627 
628         //_buffer[]  =0;
629         // At this point, f contains:
630         //    f destination array (frequency bins)
631         //    f[0...length(x)/2] = real values,
632         //    f[length(x)/2+1...length(x)-1] = imaginary values of coefficents 1...length(x)/2-1.
633         // So we have to reshuffle them to have nice complex bins.
634         int mid = _length/2;
635         outputBins[0] = Complex!T(_buffer[0], 0);
636         for(int i = 1; i < mid; ++i)
637             outputBins[i] = Complex!T(_buffer[i], _buffer[mid+i]);
638         outputBins[mid] = Complex!T(_buffer[mid], 0); // for length 1, this still works
639     }
640 
641     /**
642     * Compute the inverse FFT of the array. Perform post-scaling.
643     *
644     * Params:
645     *    inputBins = Source arrays (N/2 + 1 frequency bins).
646     *    timeData = Destination array (N time samples).
647     *
648     * Note:
649     *    This transform has the benefit you don't have to conjugate the "mirrored" part of the FFT.
650     *    Excess data in imaginary part of DC and Nyquist bins are ignored.
651     */
652     void reverseTransform(Complex!T[] inputBins, T[] timeData)
653     {
654         // On inverse transform, scale down result
655         T invMultiplier = cast(T)1 / _length;
656 
657         // Shuffle input frequency bins, and scale down.
658         int mid = _length/2;
659         for(int i = 0; i <= mid; ++i)
660             _buffer[i] = inputBins[i].re * invMultiplier;
661         for(int i = mid+1; i < _length; ++i)
662             _buffer[i] = inputBins[i-mid].im * invMultiplier;
663 
664         // At this point, the format in f is:
665         //          f [0...length(x)/2] = real values
666         //          f [length(x)/2+1...length(x)-1] = negative imaginary values of coefficents 1...length(x)/2-1.
667         // Which is suitable for the RealFFT algorithm.
668         _internal.irfft(_buffer);
669 
670         // Perf: use scaling from pfft
671         timeData[] = _buffer[];
672     }
673 
674 private:
675     // Required alignment for RFFT buffers.
676     int _alignment = -1;
677 
678     // pfft object
679     Rfft!T _internal;
680 
681     // length of FFT
682     int _length;
683 
684     // temporary buffer since pfft is in-place
685     T[] _buffer;
686 }
687 
688 
689 unittest
690 {
691     for (int i = 0; i < 16; ++i)
692     {
693         RFFT!float rfft;
694         rfft.initialize(128);
695         rfft.initialize(2048);
696     }
697 }
698 
699 /// From an impulse, computes a minimum-phase impulse
700 /// Courtesy of kasaudio, based on Aleksey Vaneev's algorithm
701 /// See: http://www.kvraudio.com/forum/viewtopic.php?t=197881
702 /// MAYDO: does it preserve amplitude?
703 ///
704 /// Params:
705 ///    tempoStorate Should be at least `tempBufferSizeForMinPhase` items.
706 void minimumPhaseImpulse(T)(T[] inoutImpulse, Complex!T[] tempStorage) nothrow @nogc // alloc free version
707 {
708     assert(tempStorage.length >= tempBufferSizeForMinPhase(inoutImpulse));
709 
710     int N = cast(int)(inoutImpulse.length);
711     int fftSize = cast(int)( nextPow2HigherOrEqual(inoutImpulse.length * 4));
712     assert(fftSize >= N);
713     int halfFFTSize = fftSize / 2;
714 
715     if (tempStorage.length < fftSize)
716         assert(false); // crash
717 
718     auto kernel = tempStorage;
719 
720     // Put the real impulse in a larger buffer
721     for (int i = 0; i < N; ++i)
722         kernel[i] = Complex!T(inoutImpulse[i], 0);
723     for (int i = N; i < fftSize; ++i)
724         kernel[i] = Complex!T(0, 0);
725 
726     forwardFFT!T(kernel[]);
727 
728     // Take the log-modulus of spectrum
729     for (int i = 0; i < fftSize; ++i)
730         kernel[i] =  Complex!T( log(std.complex.abs(kernel[i])), 0);
731 
732     // Back to real cepstrum
733     inverseFFT!T(kernel[]);
734 
735     // Apply a cepstrum window, not sure how this works
736     kernel[0] = Complex!T(kernel[0].re, 0);
737     for (int i = 1; i < halfFFTSize; ++i)
738         kernel[i] = Complex!T(kernel[i].re * 2, 0);
739     kernel[halfFFTSize] = Complex!T(kernel[halfFFTSize].re, 0);
740     for (int i = halfFFTSize + 1; i < fftSize; ++i)
741         kernel[i] = Complex!T(0, 0);
742 
743     forwardFFT!T(kernel[]);
744 
745     for (int i = 0; i < fftSize; ++i)
746         kernel[i] = complexExp!T(kernel[i]);
747 
748     inverseFFT!T(kernel[]);
749 
750     for (int i = 0; i < N; ++i)
751         inoutImpulse[i] = kernel[i].re;
752 }
753 unittest
754 {
755     double[256] impulse;
756     foreach(size_t i, ref double d; impulse)
757         d = i;
758     Complex!double[] tempStorage = new Complex!double[tempBufferSizeForMinPhase(impulse[])];
759     minimumPhaseImpulse!double(impulse[], tempStorage);
760 }
761 
762 /// Returns: Length of temporary buffer needed for `minimumPhaseImpulse`.
763 int tempBufferSizeForMinPhase(T)(T[] inputImpulse) nothrow @nogc
764 {
765     return cast(int)( nextPow2HigherOrEqual(inputImpulse.length * 4)); // PERF: too much?
766 }
767 
768 private Complex!T complexExp(T)(Complex!T z) nothrow @nogc
769 {
770     T mag = exp(z.re);
771     return Complex!T( (mag * cos(z.im)) , (mag * sin(z.im)) );
772 }