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