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 }