/// <summary> /// Fast cross-correlation via FFT /// </summary> /// <param name="signal1"></param> /// <param name="signal2"></param> /// <returns></returns> public static ComplexDiscreteSignal CrossCorrelate(ComplexDiscreteSignal signal1, ComplexDiscreteSignal signal2) { var reversedKernel = new ComplexDiscreteSignal(signal2.SamplingRate, signal2.Real.Reverse(), signal2.Imag.Reverse()); return(Convolve(signal1, reversedKernel)); }
/// <summary> /// Compute complex analytic signal /// </summary> /// <param name="samples">Array of samples</param> /// <returns>Complex analytic signal</returns> public ComplexDiscreteSignal AnalyticSignal(double[] samples) { var analyticSignal = new ComplexDiscreteSignal(1, samples); var re = analyticSignal.Real; var im = analyticSignal.Imag; _fft.Direct(re, im); for (var i = 1; i < re.Length / 2; i++) { re[i] *= 2; im[i] *= 2; } for (var i = re.Length / 2 + 1; i < re.Length; i++) { re[i] = 0.0; im[i] = 0.0; } _fft.Inverse(re, im); return(analyticSignal); }
/// <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 cross-correlation via FFT /// </summary> /// <param name="signal"></param> /// <param name="kernel"></param> /// <returns></returns> public ComplexDiscreteSignal CrossCorrelate(ComplexDiscreteSignal signal, ComplexDiscreteSignal kernel) { var reversedKernel = new ComplexDiscreteSignal(kernel.SamplingRate, kernel.Real.Reverse(), kernel.Imag.Reverse()); return(Convolve(signal, reversedKernel)); }
/// <summary> /// Compute complex analytic signal, double precision /// </summary> /// <param name="samples">Array of samples</param> /// <param name="norm">Normalize by fft size</param> /// <returns>Complex analytic signal</returns> public ComplexDiscreteSignal AnalyticSignal(double[] samples, bool norm = true) { var analyticSignal = new ComplexDiscreteSignal(1, samples); var re = analyticSignal.Real; var im = analyticSignal.Imag; _fft64.Direct(re, im); for (var i = 1; i < re.Length / 2; i++) { re[i] *= 2; im[i] *= 2; } for (var i = re.Length / 2 + 1; i < re.Length; i++) { re[i] = 0.0; im[i] = 0.0; } _fft64.Inverse(re, im); if (norm) { analyticSignal.Attenuate(re.Length); } return(analyticSignal); }
public void TestInitializeWithDifferentRealAndImagSizes() { Assert.Throws <ArgumentException>(() => { var s = new ComplexDiscreteSignal(8000, new double[] { 1, 2 }, new double[] { 3 }); }); }
/// <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))); }
public void TestInitializeWithBadSamplingRate() { Assert.Multiple(() => { Assert.Throws <ArgumentException>(() => { var s = new ComplexDiscreteSignal(0, new double[] { 1 }); }); Assert.Throws <ArgumentException>(() => { var s = new ComplexDiscreteSignal(-8000, new double[] { 1 }); }); }); }
/// <summary> /// Method for converting zeros(poles) to TF numerator(denominator) /// </summary> /// <param name="zp"></param> /// <returns></returns> public static double[] ZpToTf(ComplexDiscreteSignal zp) { var poly = new Complex[] { 1, new Complex(-zp.Real[0], -zp.Imag[0]) }; for (var k = 1; k < zp.Length; k++) { var poly1 = new Complex[] { 1, new Complex(-zp.Real[k], -zp.Imag[k]) }; poly = MathUtils.MultiplyPolynomials(poly, poly1); } return(poly.Select(p => p.Real).ToArray()); }
/// <summary> /// TF constructor from zeros/poles /// </summary> /// <param name="zeros">Zeros</param> /// <param name="poles">Poles</param> /// <param name="gain">Gain</param> public TransferFunction(ComplexDiscreteSignal zeros, ComplexDiscreteSignal poles, double gain = 1) { _zeros = zeros; _poles = poles; Denominator = poles != null?ZpToTf(poles) : new[] { 1.0 }; Numerator = zeros != null?ZpToTf(zeros) : new[] { 1.0 }; for (var i = 0; i < Numerator.Length; i++) { Numerator[i] *= gain; } }
public void TestImaginaryParts() { var s1 = new ComplexDiscreteSignal(1, new[] { 1, 0.5 }, new[] { 0, -1.5 }); var s2 = new ComplexDiscreteSignal(1, new[] { 1, 0.5 }, new[] { 0, 1.5 }); var conv = Operation.Convolve(s1, s2); Assert.Multiple(() => { Assert.That(conv.Real, Is.EquivalentTo(new[] { 1, 1, 2.5 })); Assert.That(conv.Imag, Is.EqualTo(new[] { 0, 0, 0 }).Within(1e-6)); }); }
/// <summary> /// Convert Line Spectral Frequencies to LPC coefficients /// </summary> /// <param name="lsf">The length must be equal to lpc length. Last element must be PI</param> /// <param name="lpc"></param> public static void FromLsf(float[] lsf, float[] lpc) { var n = lsf.Length - 1; var halfOrder = n / 2; var pPoles = new ComplexDiscreteSignal(1, n); var qPoles = new ComplexDiscreteSignal(1, n + 2 * (n % 2)); var k = 0; for (var i = 0; k < halfOrder; i += 2, k++) { qPoles.Real[k] = Math.Cos(lsf[i]); qPoles.Imag[k] = Math.Sin(lsf[i]); pPoles.Real[k] = Math.Cos(lsf[i + 1]); pPoles.Imag[k] = Math.Sin(lsf[i + 1]); } for (var i = 0; k < 2 * halfOrder; i += 2, k++) { qPoles.Real[k] = Math.Cos(lsf[i]); qPoles.Imag[k] = Math.Sin(-lsf[i]); pPoles.Real[k] = Math.Cos(lsf[i + 1]); pPoles.Imag[k] = Math.Sin(-lsf[i + 1]); } if (n % 2 == 1) { qPoles.Real[n] = Math.Cos(lsf[n - 1]); qPoles.Imag[n] = Math.Sin(lsf[n - 1]); qPoles.Real[n + 1] = Math.Cos(lsf[n - 1]); qPoles.Imag[n + 1] = Math.Sin(-lsf[n - 1]); } var ps = new ComplexDiscreteSignal(1, TransferFunction.ZpToTf(pPoles)); var qs = new ComplexDiscreteSignal(1, TransferFunction.ZpToTf(qPoles)); if (n % 2 == 1) { ps = Operation.Convolve(ps, new ComplexDiscreteSignal(1, new[] { 1.0, 0, -1.0 })); } else { ps = Operation.Convolve(ps, new ComplexDiscreteSignal(1, new[] { 1.0, -1.0 })); qs = Operation.Convolve(qs, new ComplexDiscreteSignal(1, new[] { 1.0, 1.0 })); } for (var i = 0; i < lpc.Length; i++) { lpc[i] = (float)(0.5 * (ps.Real[i] + qs.Real[i])); } }
/// <summary> /// Method for converting zeros(poles) to TF numerator(denominator) /// </summary> /// <param name="zp"></param> /// <returns></returns> public static double[] ZpToTf(ComplexDiscreteSignal zp) { var tf = new ComplexDiscreteSignal(1, new[] { 1.0, -zp.Real[0] }, new[] { 0.0, -zp.Imag[0] }); for (var k = 1; k < zp.Length; k++) { tf = Operation.Convolve(tf, new ComplexDiscreteSignal(1, new[] { 1.0, -zp.Real[k] }, new[] { 0.0, -zp.Imag[k] })); } return(tf.Real); }
/// <summary> /// Method for converting zeros(poles) to TF numerator(denominator) /// </summary> /// <param name="zp"></param> /// <returns></returns> public static double[] ZpToTf(ComplexDiscreteSignal zp) { if (zp == null) { throw new ArgumentException(""); } var tf = new ComplexDiscreteSignal(1, new[] { 1.0, -zp.Real[0] }, new[] { 0.0, -zp.Imag[0] }); for (var k = 1; k < zp.Length; k++) { tf = Operation.Convolve(tf, new ComplexDiscreteSignal(1, new[] { 1.0, -zp.Real[k] }, new[] { 0.0, -zp.Imag[k] })); } return(tf.Real); }
public void TestTfToSos() { var zs = new ComplexDiscreteSignal(1, new[] { 1, 0.5, -0.3, 0.2, 0.5, 0.2 }, new[] { 0, 0.2, 0, -0.9, -0.2, 0.9 }); var ps = new ComplexDiscreteSignal(1, new[] { 1, 0.2, 0.5, -0.9, 0.6, 0.1, -0.9 }, new[] { 0, 0, 0, 0.2, 0, 0, -0.2 }); var sos = DesignFilter.TfToSos(new TransferFunction(zs, ps)); Assert.Multiple(() => { Assert.That(sos[0].Numerator, Is.EqualTo(new[] { 1, -0.4, 0.85 }).Within(1e-10)); Assert.That(sos[0].Denominator, Is.EqualTo(new[] { 1, -0.1, 0 }).Within(1e-10)); Assert.That(sos[1].Numerator, Is.EqualTo(new[] { 1, -1, 0.29 }).Within(1e-10)); Assert.That(sos[1].Denominator, Is.EqualTo(new[] { 1, -0.7, 0.1 }).Within(1e-10)); Assert.That(sos[2].Numerator, Is.EqualTo(new[] { 1, 0.3, 0 }).Within(1e-10)); Assert.That(sos[2].Denominator, Is.EqualTo(new[] { 1, 1.8, 0.85 }).Within(1e-10)); Assert.That(sos[3].Numerator, Is.EqualTo(new[] { 1, -1, 0 }).Within(1e-10)); Assert.That(sos[3].Denominator, Is.EqualTo(new[] { 1, -1.6, 0.6 }).Within(1e-10)); Assert.That(sos.Length, Is.EqualTo(4)); }); }
/// <summary> /// Fast convolution via FFT for general complex-valued case /// </summary> /// <param name="signal"></param> /// <param name="kernel"></param> /// <returns></returns> public static ComplexDiscreteSignal Convolve(ComplexDiscreteSignal signal, ComplexDiscreteSignal kernel) { return(new ComplexConvolver().Convolve(signal, kernel)); }
/// <summary> /// TF constructor from zeros/poles /// </summary> /// <param name="zeros">Zeros</param> /// <param name="poles">Poles</param> /// <param name="gain">Gain</param> public TransferFunction(ComplexDiscreteSignal zeros, ComplexDiscreteSignal poles, double gain = 1) : this(zeros.ToComplexNumbers().ToArray(), poles.ToComplexNumbers().ToArray(), gain) { }
/// <summary> /// Zpk to second-order sections. /// </summary> /// <param name="tf">Transfer function</param> /// <returns>Array of SOS transfer functions</returns> public static TransferFunction[] TfToSos(TransferFunction tf) { var zeros = tf.Zeros.ToComplexNumbers().ToList(); var poles = tf.Poles.ToComplexNumbers().ToList(); if (zeros.Count != poles.Count) { if (zeros.Count > poles.Count) { poles.AddRange(new Complex[zeros.Count - poles.Count]); } if (zeros.Count < poles.Count) { zeros.AddRange(new Complex[poles.Count - zeros.Count]); } } var sosCount = (poles.Count + 1) / 2; if (poles.Count % 2 == 1) { zeros.Add(Complex.Zero); poles.Add(Complex.Zero); } RemoveConjugated(zeros); RemoveConjugated(poles); var gains = new double[sosCount]; gains[0] = tf.Gain; for (var i = 1; i < gains.Length; i++) { gains[i] = 1; } var sos = new TransferFunction[sosCount]; // reverse order of sections for (var i = sosCount - 1; i >= 0; i--) { Complex z1, z2, p1, p2; // Select the next pole closest to unit circle var pos = ClosestToUnitCircle(poles, Any); p1 = poles[pos]; poles.RemoveAt(pos); if (IsReal(p1) && poles.All(IsComplex)) { pos = ClosestToComplexValue(zeros, p1, IsReal); // closest to pole p1 z1 = zeros[pos]; zeros.RemoveAt(pos); p2 = Complex.Zero; z2 = Complex.Zero; } else { if (IsComplex(p1) && zeros.Count(IsReal) == 1) { pos = ClosestToComplexValue(zeros, p1, IsComplex); } else { pos = ClosestToComplexValue(zeros, p1, Any); } z1 = zeros[pos]; zeros.RemoveAt(pos); if (IsComplex(p1)) { p2 = Complex.Conjugate(p1); if (IsComplex(z1)) { z2 = Complex.Conjugate(z1); } else { pos = ClosestToComplexValue(zeros, p1, IsReal); z2 = zeros[pos]; zeros.RemoveAt(pos); } } else { if (IsComplex(z1)) { z2 = Complex.Conjugate(z1); pos = ClosestToComplexValue(poles, z1, IsReal); p2 = poles[pos]; poles.RemoveAt(pos); } else { pos = ClosestToUnitCircle(poles, IsReal); p2 = poles[pos]; poles.RemoveAt(pos); pos = ClosestToComplexValue(zeros, p2, IsReal); z2 = zeros[pos]; zeros.RemoveAt(pos); } } } var zs = new ComplexDiscreteSignal(1, new[] { z1.Real, z2.Real }, new[] { z1.Imaginary, z2.Imaginary }); var ps = new ComplexDiscreteSignal(1, new[] { p1.Real, p2.Real }, new[] { p1.Imaginary, p2.Imaginary }); sos[i] = new TransferFunction(zs, ps, gains[i]); } return(sos); }
/// <summary> /// FIR filter design using frequency sampling method /// </summary> /// <param name="order">Filter order</param> /// <param name="frequencyResponse">Complex frequency response</param> /// <param name="window">Window</param> /// <returns>FIR filter kernel</returns> public static double[] Fir(int order, ComplexDiscreteSignal frequencyResponse, WindowTypes window = WindowTypes.Blackman) { return(Fir(order, frequencyResponse.Real, frequencyResponse.Imag, window)); }
/// <summary> /// TF constructor from zeros/poles /// </summary> /// <param name="zeros"></param> /// <param name="poles"></param> public TransferFunction(ComplexDiscreteSignal zeros, ComplexDiscreteSignal poles) { Zeros = zeros; Poles = poles; }
/// <summary> /// TF constructor from zeros/poles /// </summary> /// <param name="zeros">Zeros</param> /// <param name="poles">Poles</param> /// <param name="gain"></param> public TransferFunction(ComplexDiscreteSignal zeros, ComplexDiscreteSignal poles, double gain = 1.0) { Gain = gain; Zeros = zeros; Poles = poles; }
/// <summary> /// Method for converting zeros(poles) to TF numerator(denominator) /// </summary> /// <param name="zp"></param> /// <returns></returns> public static double[] ZpToTf(ComplexDiscreteSignal zp) { return(ZpToTf(zp.ToComplexNumbers().ToArray())); }
/// <summary> /// Fast complex cross-correlation via FFT /// </summary> /// <param name="signal1"></param> /// <param name="signal2"></param> /// <returns></returns> public static ComplexDiscreteSignal CrossCorrelate(ComplexDiscreteSignal signal1, ComplexDiscreteSignal signal2) { return(new ComplexConvolver().CrossCorrelate(signal1, signal2)); }