예제 #1
0
        // ********************************************************************************
        /// <summary>
        /// Single Tone Analysis
        /// </summary>
        /// <param name="timewaveform">Waveform in time space</param>
        /// <param name="Fs">Sampling frequency, unit in Hz</param>
        /// <param name="initialGuess">Initial guess for the tone frequency, unit in Hz</param>
        /// <param name="searchRange">Peak search range near the initialGuess.</param>
        /// <returns>Tone information of the signal. Contains amplitude, frequency and phase.</returns>
        /// <created>Wei Jin,2019/11/29</created>
        /// <changed>Wei Jin,2019/11/29</changed>
        // ********************************************************************************
        public static ToneInfo SingleToneAnalysis(double[] timewaveform, double Fs = 1.0, double initialGuess = 0, double searchRange = 0.05)
        {
            ToneInfo toneInfo;
            int      i;

            int fftSize = timewaveform.Length;

            Complex[] spectrum = new Complex[fftSize];

            Spectrum.AdvanceComplexFFT(timewaveform, WindowType.Hanning, ref spectrum);

            // Use the center position as time '0' reference, which makes the spectrum of the window function pure real number
            for (i = 0; i < (fftSize + 1) / 4; i++)
            {
                spectrum[i * 2 + 1] = -spectrum[i * 2 + 1];
            }

            // Null DC components
            for (i = 0; i < 2; i++)
            {
                spectrum[i] = 0;
            }

            // Establish tone search range
            int searchStart = 2;
            int searchEnd   = fftSize / 2 - 2;

            if (initialGuess > 0 && initialGuess < Fs / 2.0)
            {
                searchStart = Math.Max(searchStart, (int)((initialGuess / Fs - searchRange / 2) * fftSize));
                searchEnd   = Math.Min(searchEnd, (int)((initialGuess / Fs + searchRange / 2) * fftSize));
            }

            // Gross search for the peak tone
            double peakVal = 0;
            int    peakPos = 0;

            for (i = searchStart; i < searchEnd; i++)
            {
                if (peakVal < spectrum[i].Magnitude)
                {
                    peakVal = spectrum[i].Magnitude;
                    peakPos = i;
                }
            }

            // Refine peak result for the first round
            Complex[] threeFingers = new Complex[3];
            Array.Copy(spectrum, peakPos - 1, threeFingers, 0, 3);
            toneInfo            = FFTPeakCorrection(threeFingers);
            toneInfo.Frequency += peakPos - 1;

            // Remove aliasing around DC and Fs/2
            // The Fourier transform of the hanning window has the following form
            //      h(z)    = sinc(z) / (z * z - 1)
            //              = sin(pi * z) / (pi * z * (z * z - 1))
            // Note: the negative frequency component has conjugate phase
            // Question(wjin): Why use add instead of sub?
            double x_offset;

            for (i = 0; i < 3; i++)
            {
                x_offset         = peakPos - 1 + i + toneInfo.Frequency;
                threeFingers[i] += Trig.Sinc(x_offset) / (x_offset * x_offset - 1.0)
                                   * Complex.FromPolarCoordinates(toneInfo.Amplitude, -toneInfo.Phase);

                x_offset        -= fftSize;
                threeFingers[i] += Trig.Sinc(x_offset) / (x_offset * x_offset - 1.0)
                                   * Complex.FromPolarCoordinates(toneInfo.Amplitude, -toneInfo.Phase);
            }

            // Refine peak result for the second round
            toneInfo            = FFTPeakCorrection(threeFingers);
            toneInfo.Frequency += peakPos - 1;

            // Correct the results for output.
            toneInfo.Amplitude *= 2.0 / fftSize;
            toneInfo.Phase      = _PhaseSub(toneInfo.Phase, toneInfo.Frequency * Math.PI); // Change the reference position to the begining of the singal
            toneInfo.Phase      = _PhaseSub(toneInfo.Phase, -0.5 * Math.PI);               // Change Cos phase to Sin phase
            toneInfo.Frequency *= Fs / fftSize;                                            // Convert frequency unit from 'bins' to engineering units.

            return(toneInfo);
        }