/// <summary> /// Fast convolution via FFT for general complex-valued case /// </summary> /// <param name="signal"></param> /// <param name="kernel"></param> /// <returns></returns> public ComplexDiscreteSignal Convolve(ComplexDiscreteSignal signal, ComplexDiscreteSignal kernel) { var length = signal.Length + kernel.Length - 1; var fftSize = MathUtils.NextPowerOfTwo(length); var fft = new Fft64(fftSize); signal = signal.ZeroPadded(fftSize); kernel = kernel.ZeroPadded(fftSize); // 1) do FFT of both signals fft.Direct(signal.Real, signal.Imag); fft.Direct(kernel.Real, kernel.Imag); // 2) do complex multiplication of spectra var spectrum = signal.Multiply(kernel); // 3) do inverse FFT of resulting spectrum fft.Inverse(spectrum.Real, spectrum.Imag); // 3a) normalize for (var i = 0; i < spectrum.Length; i++) { spectrum.Real[i] /= fftSize; spectrum.Imag[i] /= fftSize; } // 4) return resulting meaningful part of the signal (truncate size to N + M - 1) return(new ComplexDiscreteSignal(signal.SamplingRate, spectrum.Real, spectrum.Imag).First(length)); }
/// <summary> /// Fast deconvolution via FFT for general complex-valued case. /// /// NOTE! /// /// Deconvolution is an experimental feature. /// It's problematic due to division by zero. /// /// </summary> /// <param name="signal"></param> /// <param name="kernel"></param> /// <param name="fftSize"></param> /// <returns></returns> public ComplexDiscreteSignal Deconvolve(ComplexDiscreteSignal signal, ComplexDiscreteSignal kernel, int fftSize = 0) { // first, try to divide polynomials var div = MathUtils.DividePolynomial(signal.Real.Zip(signal.Imag, (r, i) => new Complex(r, i)).ToArray(), kernel.Real.Zip(kernel.Imag, (r, i) => new Complex(r, i)).ToArray()); var quotient = div[0]; var remainder = div[1]; if (remainder.All(d => Math.Abs(d.Real) < 1e-10) && remainder.All(d => Math.Abs(d.Imaginary) < 1e-10)) { return(new ComplexDiscreteSignal(signal.SamplingRate, quotient.Select(q => q.Real), quotient.Select(q => q.Imaginary))); } // ... deconvolve via FFT var length = signal.Length - kernel.Length + 1; if (fftSize == 0) { fftSize = MathUtils.NextPowerOfTwo(signal.Length); } var fft = new Fft64(fftSize); signal = signal.ZeroPadded(fftSize); kernel = kernel.ZeroPadded(fftSize); // 1) do FFT of both signals fft.Direct(signal.Real, signal.Imag); fft.Direct(kernel.Real, kernel.Imag); for (var i = 0; i < fftSize; i++) { signal.Real[i] += 1e-10; signal.Imag[i] += 1e-10; kernel.Real[i] += 1e-10; kernel.Imag[i] += 1e-10; } // 2) do complex division of spectra var spectrum = signal.Divide(kernel); // 3) do inverse FFT of resulting spectrum fft.Inverse(spectrum.Real, spectrum.Imag); // 4) return resulting meaningful part of the signal (truncate to N - M + 1) return(new ComplexDiscreteSignal(signal.SamplingRate, spectrum.Real.FastCopyFragment(length), spectrum.Imag.FastCopyFragment(length))); }
/// <summary> /// Frequency response of an FIR filter is the FT of its impulse response /// </summary> public override ComplexDiscreteSignal FrequencyResponse(int length = 512) { var real = Kernel.PadZeros(length); var imag = new double[length]; var fft = new Fft64(length); fft.Direct(real, imag); return(new ComplexDiscreteSignal(1, real.Take(length / 2 + 1), imag.Take(length / 2 + 1))); }
/// <summary> /// Returns the complex frequency response of a filter. /// /// Method calculates the Frequency Response of a filter /// by taking FFT of an impulse response (possibly truncated). /// </summary> /// <param name="length">Number of frequency response samples</param> public virtual ComplexDiscreteSignal FrequencyResponse(int length = 512) { var real = ImpulseResponse(length); var imag = new double[length]; var fft = new Fft64(length); fft.Direct(real, imag); return(new ComplexDiscreteSignal(1, real.Take(length / 2 + 1), imag.Take(length / 2 + 1))); }
/// <summary> /// Evaluate frequency response /// </summary> /// <param name="length"></param> /// <returns></returns> public ComplexDiscreteSignal FrequencyResponse(int length = 512) { var ir = ImpulseResponse(length); var real = ir.Length == length ? ir : ir.Length < length?ir.PadZeros(length) : ir.FastCopyFragment(length); var imag = new double[length]; var fft = new Fft64(length); fft.Direct(real, imag); return(new ComplexDiscreteSignal(1, real.Take(length / 2 + 1), imag.Take(length / 2 + 1))); }
/// <summary> /// Group delay calculated from TF coefficients /// </summary> public double[] GroupDelay(int fftSize = 512) { var cc = Operation.CrossCorrelate(new ComplexDiscreteSignal(1, Numerator), new ComplexDiscreteSignal(1, Denominator)).Real; var cr = Enumerable.Range(0, cc.Length) .Zip(cc, (r, c) => r * c) .ToArray(); var re = cc.PadZeros(fftSize); var im = new double[fftSize]; var fft = new Fft64(fftSize); fft.Direct(re, im); var rre = cr.PadZeros(fftSize); var rim = new double[fftSize]; fft.Direct(rre, rim); var num = rre.Zip(rim, (r, i) => new Complex(r, i)).ToArray(); var den = re.Zip(im, (r, i) => new Complex(r, i)).ToArray(); var dn = Numerator.Length - 1; var gd = new double[fftSize / 2]; for (var i = 1; i <= gd.Length; i++) { if (Complex.Abs(den[i]) < 1e-10) { num[i] = Complex.Zero; den[i] = Complex.One; } var t = dn - num[i] / den[i]; gd[i - 1] = t.Real; } return(gd); }
/// <summary> /// FIR filter design using frequency sampling method /// </summary> /// <param name="order">Filter order</param> /// <param name="magnitudeResponse">Magnitude response</param> /// <param name="phaseResponse">Phase response</param> /// <param name="window">Window</param> /// <returns>FIR filter kernel</returns> public static double[] Fir(int order, double[] magnitudeResponse, double[] phaseResponse = null, WindowTypes window = WindowTypes.Blackman) { Guard.AgainstEvenNumber(order, "The order of the filter"); var fftSize = MathUtils.NextPowerOfTwo(magnitudeResponse.Length); var real = phaseResponse == null? magnitudeResponse.PadZeros(fftSize) : magnitudeResponse.Zip(phaseResponse, (m, p) => m * Math.Cos(p)).ToArray(); var imag = phaseResponse == null ? new double[fftSize] : magnitudeResponse.Zip(phaseResponse, (m, p) => m * Math.Sin(p)).ToArray(); var fft = new Fft64(fftSize); fft.Inverse(real, imag); var kernel = new double[order]; var compensation = 2.0 / fftSize; var middle = order / 2; for (var i = 0; i <= middle; i++) { kernel[i] = real[middle - i] * compensation; kernel[i + middle] = real[i] * compensation; } kernel.ApplyWindow(window); return(kernel); }
/// <summary> /// Method for FIR filter design using window method /// </summary> /// <param name="order"></param> /// <param name="magnitudeResponse"></param> /// <param name="phaseResponse"></param> /// <param name="window"></param> /// <returns></returns> public static FirFilter Fir(int order, double[] magnitudeResponse, double[] phaseResponse = null, WindowTypes window = WindowTypes.Blackman) { if (order % 2 == 0) { throw new ArgumentException("The order of a filter must be an odd number!"); } var fftSize = MathUtils.NextPowerOfTwo(magnitudeResponse.Length); var real = phaseResponse == null? magnitudeResponse.PadZeros(fftSize) : magnitudeResponse.Zip(phaseResponse, (m, p) => m * Math.Cos(p)).ToArray(); var imag = phaseResponse == null ? new double[fftSize] : magnitudeResponse.Zip(phaseResponse, (m, p) => m * Math.Sin(p)).ToArray(); var fft = new Fft64(fftSize); fft.Inverse(real, imag); var kernel = new double[order]; var compensation = 2.0 / fftSize; var middle = order / 2; for (var i = 0; i <= middle; i++) { kernel[i] = real[middle - i] * compensation; kernel[i + middle] = real[i] * compensation; } kernel.ApplyWindow(window); return(new FirFilter(kernel)); }