/// <summary> /// This algorithm is derived from P 154 (Table 8-1) of ISBN 0-9660176-3-3 "The Scientist and Engineer's Guide to Digital Signal Processing" /// </summary> /// <param name="frequencyDomain"></param> /// <returns></returns> public List <double> Synthesize(FrequencyDomain frequencyDomain) { int freqDomainLen = frequencyDomain.RealComponent.Count; int timeDomainLen = freqDomainLen * 2 - 1; List <double> timeDomain = new List <double>(timeDomainLen); List <double> cosineAmplitudes = new List <double>(freqDomainLen); List <double> sineAmplitudes = new List <double>(freqDomainLen); for (int K = 0; K < freqDomainLen; K++) { cosineAmplitudes.Add(frequencyDomain.ScaledRealComponent(K) / (timeDomainLen / 2)); sineAmplitudes.Add(-1 * frequencyDomain.ScaledImaginaryComponent(K) / (timeDomainLen / 2)); } // Fixup the endpoints cosineAmplitudes[0] = cosineAmplitudes[0] / 2; cosineAmplitudes[freqDomainLen - 1] = cosineAmplitudes[freqDomainLen - 1] / 2; for (int I = 0; I < timeDomainLen; I++) { timeDomain.Add(0.0); } for (int K = 0; K < freqDomainLen; K++) { for (int I = 0; I < timeDomainLen; I++) { timeDomain[I] += cosineAmplitudes[K] * Math.Cos(2 * Math.PI * K * I / timeDomainLen); timeDomain[I] += sineAmplitudes[K] * Math.Sin(2 * Math.PI * K * I / timeDomainLen); } } return(timeDomain); }
// Copy constructor public FrequencyDomain(FrequencyDomain rh) { this.RealComponent = new List <double>(rh.RealComponent); this.ScalingFactor = new List <double>(rh.ScalingFactor); this.ImaginaryComponent = new List <double>(rh.ImaginaryComponent); this.frequencyDomainLen = rh.frequencyDomainLen; }
/// <summary> /// Adapted from Udemy course "Master the Fourier transform and its applications" by Mike X Cohen /// </summary> /// <param name="timeDomain">Time series data</param> /// <returns></returns> public FrequencyDomain Transform(List <double> timeDomain, double sampleRateHz) { FrequencyDomain result = FindFourierCoeffecients(timeDomain, sampleRateHz); LoadFrequencyAmplitudes(result); return(result); }
private static FrequencyDomain FindFourierCoeffecients(List <double> timeDomain, double sampleRateHz) { int timeDomainLen = timeDomain.Count; int numProcs = Environment.ProcessorCount; int concurrencyLevel = numProcs * 2; // Prepare the Fourier Transform Dictionary <int, double> fourTime = new Dictionary <int, double>(timeDomainLen); ConcurrentDictionary <int, Complex> csw = new ConcurrentDictionary <int, Complex>(concurrencyLevel, timeDomainLen); List <Complex> fCoefs = new List <Complex>(timeDomainLen); // Setup the fourier time array for (int idx = 0; idx < timeDomainLen; idx++) { fourTime.Add(idx, ((double)idx / (double)timeDomainLen)); } Complex negOneI = new Complex(0, -1); Complex euler = new Complex(Math.E, 0); Complex exponentMultiplier = negOneI * Math.PI * 2; for (int fi = 1; fi <= timeDomainLen; fi++) { Parallel.ForEach(fourTime, (timeVal) => { Complex value = Complex.Pow(euler, (exponentMultiplier * (fi - 1) * timeVal.Value)); csw[timeVal.Key] = value; }); // Compute dot product between signal and complex sine wave Complex dotProduct = new Complex(0, 0); object dotProductLock = new object(); Parallel.For(0, (timeDomainLen - 1), (idx) => { Complex dotProductLocal = new Complex(0, 0); dotProductLocal += ((csw[idx] * timeDomain[idx])); lock (dotProductLock) { dotProduct += dotProductLocal; } } ); fCoefs.Add(dotProduct); csw.Clear(); } FrequencyDomain result = new FrequencyDomain(); result.FourierCoefficients = fCoefs; result.SampleRateHz = sampleRateHz; return(result); }
public static List <Tuple <double, double> > ToMagnitudePhaseList(FrequencyDomain frequencyDomain) { List <Tuple <double, double> > magPhaseList = new List <Tuple <double, double> >(); foreach (Complex coefficient in frequencyDomain.FourierCoefficients) { magPhaseList.Add(new Tuple <double, double>(coefficient.Magnitude, coefficient.Phase)); } return(magPhaseList); }
/// <summary> /// Simple pass through , callers typically parse magnitude/phase CSVs and pass in a list of magnitude and phases which we /// then assign to a new frequencyDomain object and return. /// </summary> /// <param name="magPhaseList"></param> /// <returns></returns> public static FrequencyDomain FromMagnitudePhaseList(List <Tuple <double, double> > magPhaseList) { if (null == magPhaseList) { return(null); } FrequencyDomain frequencyDomain = new FrequencyDomain(); foreach (Tuple <double, double> magPhase in magPhaseList) { frequencyDomain.FourierCoefficients.Add(Complex.FromPolarCoordinates(magPhase.Item1, magPhase.Item2)); } return(frequencyDomain); }
/// <summary> /// Adapted from Udemy course "Master the Fourier transform and its applications" by Mike X Cohen /// </summary> /// <param name="freqDomain">Time Frequency Domain data</param> /// <returns></returns> public List <double> Synthesize(FrequencyDomain freqDomain) { List <Complex> fCoefs = freqDomain.FourierCoefficients; int freqDomainLen = fCoefs.Count; List <double> timeDomain = new List <double>(freqDomainLen); List <Complex> csw = new List <Complex>(freqDomainLen); List <Complex> reconstructedSignal = new List <Complex>(freqDomainLen); List <double> fourTime = new List <double>(freqDomainLen); // Setup the fourier time array for (int idx = 0; idx < freqDomainLen; idx++) { fourTime.Add((double)idx / (double)freqDomainLen); reconstructedSignal.Add(new Complex(0, 0)); } for (int fi = 1; fi <= freqDomainLen; fi++) { Complex posOneI = new Complex(0, 1); Complex euler = new Complex(Math.E, 0); foreach (double timeVal in fourTime) { Complex value = fCoefs[fi - 1] * Complex.Pow(euler, (posOneI * Math.PI * 2 * (fi - 1) * timeVal)); csw.Add(value); } for (int idx = 0; idx < csw.Count; idx++) { reconstructedSignal[idx] += csw[idx]; } csw.Clear(); } for (int idx = 0; idx < reconstructedSignal.Count; idx++) { reconstructedSignal[idx] /= freqDomainLen; } foreach (Complex sigVal in reconstructedSignal) { timeDomain.Add(sigVal.Real); } return(timeDomain); }
/// <summary> /// This algorithm is derived from Chapter 12 (table 12-4) of ISBN 0-9660176-3-3 "The Scientist and Engineer's Guide to Digital Signal Processing" /// </summary> /// <param name="timeDomain"></param> /// <returns></returns> public FrequencyDomain Transform(List <double> timeDomain, double sampleRateHz) { FrequencyDomain fftResult = new FrequencyDomain(timeDomain.Count, this); // The FFT operates in place, store the timeDomain samples in the real component (leave the imaginary compoenent zeroed) fftResult.RealComponent.Clear(); fftResult.RealComponent.AddRange(timeDomain); fftResult.ImaginaryComponent.Clear(); fftResult.ImaginaryComponent.Capacity = timeDomain.Count; fftResult.SampleRateHz = sampleRateHz; for (int idx = 0; idx < timeDomain.Count; idx++) { fftResult.ImaginaryComponent.Add(0.0); } return(Transform(fftResult)); }
/// <summary> /// Discrete Fourier Transform Analysis /// Derived from Chapter 31 of the "The Scientist and Engineer's Guide to Digital Signal Processing /// https://www.dspguide.com/CH31.PDF /// </summary> /// <param name="signal"></param> /// <param name="sampleRateHz"></param> /// <returns></returns> public FrequencyDomain Transform(List <double> signal, double sampleRateHz) { if (signal == null) { return(null); } // Note index variables and arrays named to coincide with equation on page Chapter 31, page 578, // Discrete Fourier Transform (DFT) analysis List <double> x = signal; List <Complex> X = new List <Complex>(x.Count); int N = x.Count; Complex j = new Complex(0, 1); Complex exponentPart = -1 * j * Math.PI * 2; FrequencyDomain frequencyDomain = null; object xLock = new object(); for (int k = 0; k <= (N - 1); k++) { X.Add(new Complex(0, 0)); } // k runs over period 0 to N - 1 Parallel.For(0, (N - 1), (k) => { Complex Xk = new Complex(0, 0); //X[k] = 1/N * (Sum from 0 to N - 1 of x[n] * e ^ (-j * 2 * Pi * k * n / N for (int n = 0; n <= (N - 1); n++) { double knOverN = (double)(k * n) / (double)N; Xk += x[n] * Complex.Pow(Math.E, (exponentPart * knOverN)); } lock (xLock) { X[k] = Xk / N; // Normalize X[k] (1/N multiplier) } }); frequencyDomain = new FrequencyDomain(); frequencyDomain.FourierCoefficients = X; frequencyDomain.SampleRateHz = sampleRateHz; LoadFrequencyAmplitudes(frequencyDomain); return(frequencyDomain); }
/// <summary> /// This algorithm is derived from P 160 (Table 8-2) of ISBN 0-9660176-3-3 "The Scientist and Engineer's Guide to Digital Signal Processing" /// </summary> /// <param name="timeDomain"></param> /// <returns></returns> public FrequencyDomain Transform(List <double> timeDomain, double sampleRateHz) { int timeDomainLen = timeDomain.Count; FrequencyDomain frequencyDomain = new FrequencyDomain(timeDomainLen, this); frequencyDomain.SampleRateHz = sampleRateHz; int freqDomainLen = frequencyDomain.RealComponent.Count; for (int K = 0; K < freqDomainLen; K++) { for (int I = 0; I < timeDomainLen; I++) { frequencyDomain.RealComponent[K] += timeDomain[I] * Math.Cos(2 * Math.PI * K * I / timeDomainLen); frequencyDomain.ImaginaryComponent[K] -= timeDomain[I] * Math.Sin(2 * Math.PI * K * I / timeDomainLen); } } return(frequencyDomain); }
private static void LoadFrequencyAmplitudes(FrequencyDomain frequencyDomain) { double sampleRate = frequencyDomain.SampleRateHz; int points = frequencyDomain.FourierCoefficients.Count; List <double> frequencyVector = linspace(0, sampleRate / 2, (int)Math.Floor((double)((points / 2) + 1))); List <double> amplitudesVector = new List <double>(points); foreach (Complex coefficient in frequencyDomain.FourierCoefficients) { double amplitude = 2 * (Complex.Abs(coefficient) / points); amplitudesVector.Add(amplitude); } for (int idx = 0; idx < frequencyVector.Count; idx++) { frequencyDomain.FrequencyAmplitudes.Add(frequencyVector[idx], amplitudesVector[idx]); } }
/// <summary> /// This algorithm is derived from Table 12-5 of ISBN 0-9660176-3-3 "The Scientist and Engineer's Guide to Digital Signal Processing" /// Inverse Fourier Transform /// </summary> /// <param name="frequencyDomain"></param> /// <returns></returns> public List <double> Synthesize(FrequencyDomain frequencyDomain) { for (int k = 0; k < frequencyDomain.frequencyDomainLen; k++) { frequencyDomain.ImaginaryComponent[k] *= -1; } // Transformation happens in place Transform(frequencyDomain); // Chainge the sign of the imaginary component for (int i = 0; i < frequencyDomain.frequencyDomainLen; i++) { frequencyDomain.RealComponent[i] /= frequencyDomain.frequencyDomainLen; frequencyDomain.ImaginaryComponent[i] = -1 * frequencyDomain.ImaginaryComponent[i] / frequencyDomain.frequencyDomainLen; } return(frequencyDomain.RealComponent); }
/// <summary> /// Discrete Fourier Transform Synthesis /// Derived from Chapter 31 of the "The Scientist and Engineer's Guide to Digital Signal Processing /// https://www.dspguide.com/CH31.PDF /// </summary> /// <param name="frequencyDomain"></param> /// <returns>The real portion of the time domain signal</returns> public List <double> Synthesize(FrequencyDomain frequencyDomain) { if (frequencyDomain == null) { return(null); } // Note index variables and arrays named to coincide with equation on page Chapter 31, page 578, // Discrete Fourier Transform (DFT) synthesis int N = frequencyDomain.FourierCoefficients.Count; List <double> realTimeDomain = new List <double>(N); List <Complex> X = frequencyDomain.FourierCoefficients; Complex j = new Complex(0, 1); Complex exponentPart = j * Math.PI * 2; object realTimeDomainLock = new object(); for (int n = 0; n <= (N - 1); n++) { realTimeDomain.Add(0.0); } Parallel.For(0, (N - 1), (n) => { Complex xn = new Complex(0, 0); for (int k = 0; k <= (N - 1); k++) { double knOverN = (double)(k * n) / (double)N; xn += X[k] * Complex.Pow(Math.E, exponentPart * knOverN); } lock (realTimeDomainLock) { realTimeDomain[n] = xn.Real; } }); return(realTimeDomain); }
private FrequencyDomain Transform(FrequencyDomain frequencyDomain) { int n = frequencyDomain.RealComponent.Count; int nm1 = n - 1; int nd2 = n / 2; int m = (int)(Math.Log(n, 2)); int j = nd2; #region bitreversal int k; for (int idx = 1; idx <= (n - 2); idx++) { if (idx <= j) { double tr = frequencyDomain.RealComponent[j]; double ti = frequencyDomain.ImaginaryComponent[j]; frequencyDomain.RealComponent[j] = frequencyDomain.RealComponent[idx]; frequencyDomain.ImaginaryComponent[j] = frequencyDomain.ImaginaryComponent[idx]; frequencyDomain.RealComponent[idx] = tr; frequencyDomain.ImaginaryComponent[idx] = ti; } k = nd2; while (!(k > j)) { j = j - k; if (k >= 2) { k = k / 2; } } j = j + k; } #endregion #region stageLoop for (int L = 1; L <= m; L++) { int le = (int)Math.Pow(2, L); int le2 = le / 2; double ur = 1; double ui = 0; double sr = Math.Cos(Math.PI / le2); double si = -1 * Math.Sin(Math.PI / le2); double tr; double ti; for (j = 1; j <= le2; j++) { int jm1 = j - 1; for (int idx = jm1; idx <= nm1; idx += le) { int ip = (idx + le2); tr = frequencyDomain.RealComponent[ip] * ur - frequencyDomain.ImaginaryComponent[ip] * ui; ti = frequencyDomain.RealComponent[ip] * ui + frequencyDomain.ImaginaryComponent[ip] * ur; frequencyDomain.RealComponent[ip] = frequencyDomain.RealComponent[idx] - tr; frequencyDomain.ImaginaryComponent[ip] = frequencyDomain.ImaginaryComponent[idx] - ti; frequencyDomain.RealComponent[idx] = frequencyDomain.RealComponent[idx] + tr; frequencyDomain.ImaginaryComponent[idx] = frequencyDomain.ImaginaryComponent[idx] + ti; } tr = ur; ur = tr * sr - ui * si; ui = tr * si + ui * sr; } } #endregion return(frequencyDomain); }