View Raw SPL
/*****************************************************************************
*                                                                            *
*   SINFIT.SPL    Copyright (C) 1999-2012 DSP Development Corporation        *
*                               All Rights Reserved                          *
*                                                                            *
*   Author:       Randy Race                                                 *
*                                                                            *
*   Synopsis:     Fits y(x) = A + B * sin(C*x + D) using the FFT             *
*                                                                            *
*   Revisions:    22 Jun 1999  RRR  Creation                                 *
*                  7 May 2012  RRR  Hanning window interpolation method      *
*                                                                            *
*****************************************************************************/


#if @HELP_SINFIT

    SINFIT

    Purpose: Fits y(x) = A + B * sin(C*x + D) using the FFT

    Syntax:  SINFIT(s, wfreq)

                  s - A series, the input.

              wfreq - Optional. A real, the known frequency in radians.
                      Defaults to -1, estimate frequency.


    Returns: A series, the fitted sine curve

             (fit, coef) = sinfit(s)

             returns both the fit and the coefficients as a series.


    Example:
             W1: 5 + 3 * gsin(100, .01, 4, pi) + gnorm(100, .01)

             W2: sinfit(w1);overp(w1, lred)

             Overplots the original data with the calculated sin fit.


             (fit, coef) = sinfit(w1)

             fit is the same series as in W2

             coef == {5.0000, 2.9737, 25.1554, 3.1449}

             Note: since C == coef[3] == 2*pi*F, in this case,
                   F == 25.1554 / (2*pi) = 4.004

    Remarks:
             SINFIT uses the FFT to find the dominate frequency present
             in the series by multiplying the input with a Hanning
             window and using Hanning window interpolation in the
             frequency domain to estimate the frequency, magnitude and
             phase.  The Hanning window interpolation scheme handles
             estimated frequencies that do not precisely correspond to
             FFF frequency bin values.

             The frequency term, C == coef[3] and phase term, D == coef[4],
             are in radians.

             SINFIT3 uses LSINFIT to perform the least squares fit as
             per the IEEE 1241 Standard.

             See SINFIT4 for an implementation of the 4 term iterative
             sinusoid fit as outlined in the IEEE 1241 Standard.

             See SINFIT for a similar routine that uses Hanning window
             interpolation and returns the frequency coefficient in
             radians.

             See SINTREND to fit a sine with a linear trend.

    See Also:
             FFT
             Hanning
             Sinfit3
             Sinfit4
             Sintrend
             Trend
#endif



/* fit y = A + B * sin(C*x + D) */
ITERATE sinfit(s, wfreq)
{
        if (argc < 2)
        {
                if (argc < 1) error("sinfit - input series required");

                /* estimate frequency */
                wfreq = -1;
        }

        /* devolved case */
        if (length(s) <= 1)
        {
                if (wfreq < 0)
                {
                        wfreq = 0;
                }

                return({max(s)}, {0, max(s), wfreq, 0});
        }

        /* original xvals */
        xv = xvals(s);

        if (isxy(s))
        {
                /* interpolate XY to interval series */
                s = xyinterp(s);
        }

        /* best power of 2 FFT length */
        n = bestpow2(s);

        /* hanning windowed FFT */
        f = fft(hanning(demean(s), n));

        /* first half */
        f1 = extract(f, 1, int(n/2));

        /* normalized magnitude */
        fmag = 4 * mag(f1) / length(s);

        if (wfreq < 0)
        {
                /* index of primary frequency */
                kmax = maxidx(fmag);

                /* use left or right side of max for interpolation */
                kleft = (kmax > 1) ? (fmag[kmax - 1] > fmag[kmax + 1]) : 0;

                /* hanning window interpolation */
                if (kleft)
                {
                        dr  = fmag[kmax - 1] / max(fmag);
                        dfx = (kmax - 1) + (1.0 - 2.0 * dr) / (1.0 + dr);
                }
                else
                {
                        dr  = fmag[kmax + 1] / max(fmag);
                        dfx = (kmax - 1) - (1.0 - 2.0 * dr) / (1.0 + dr);
                }

                /* frequency in Hz */
                C = dfx * deltax(fmag);
        }
        else
        {
                /* known frequency, set kmax to get magnitude and phase */
                C    = wfreq / (2 * pi);
                dfx  = C / deltax(fmag);
                kmax = int(dfx) + 1;
        }

        /* convert to radian frequency */
        C *= 2 * pi;

        /* find magnitude and phase */
        dx = dfx - kmax + 1;
        dy = pi * dx;

        /* magnitude */
        if (abs(dx) <= eps)
        {
                B = fmag[kmax];
        }
        else
        {
                B = -fmag[kmax] * (dy / sin(dy)) * (dx - 1.0) * (dx + 1.0);
        }

        /* phase in radians */
        cx = f1[kmax];
        cy = cos(dy) - i * sin(dy);
        cz = cx * cy;

        D = atan2(imag(cz), real(cz));

        /* add pi/2 to reference to sine */
        D += pi/2;

        /* account for X offset */
        D -= C * xoffset(s);

        /* build fit and coefficients */
        fit = B * sin(C * xv + D);

        /* add mean to fit */
        A = mean(s - fit);

        fit += A;

        /* units */
        sethunits(fit, gethunits(s));
        setvunits(fit, getvunits(s));

        /* coefficients */
        coef = {A, B, C, D};

        if (outargc > 1)
        {
                return(fit, coef);
        }
        else
        {
                return(fit);
        }
}