View Raw SPL
/*****************************************************************************
*                                                                            *
*   DEMODPM.SPL  Copyright (C) 2019 DSP Development Corporation              *
*                               All Rights Reserved                          *
*                                                                            *
*   Author:      Randy Race                                                  *
*                                                                            *
*   Synopsis:    Phase Demodulation using the Hilbert Transform              *
*                                                                            *
*   Revisions:   10 Jan 2019 RRR  Creation                                   *
*                                                                            *
*****************************************************************************/

#if @HELP_DEMODPM

    DEMODPM

    Purpose: Demodulates a phase modulated series using the Hilbert Transform

    Syntax:  DEMODPM(s, fc, pdev, p0)

              s - the input series

             fc - Optional, a real. The center frequency of the phase
                  modulated result. Defaults to -1, estimate the center
                  frequency using the FFT.

           pdev - Optional, a real. The phase deviation. Defaults to 
                  pi / 2;

             p0 - Optional, a real. The initial phase. Defaults to 0.0.

    Returns: A series or array, the demodulated series.

    Example:
             W1: gtriwave(1000,.001, 4)
             W2: W1 * 100 + 20
             W3: modpm(w1)
             W4: demodpm(w3)

             W2 represents the scaled information signal and W3 is the
             resulting phase modulated signal. The amplitude of W2
             determines the instantaneous phase of W3. The instantaneous
             phase of W3 ranges from:

                             min(w3) == 0 radians

                                     to

                             max(w3) == pi / 2 radians


             W4 is the demodulated result.

    Remarks:
             DEMODPM uses HILB to calculate the Hilbert Transform.

             If FC is < 0, the center frequency is estimated using
             SINFIT3.

             See MODPM to phase modulate a series.

    See Also:
             Demodam
             Demodfm
             Hilb
             Modam
             Modfm
             Modpm
             Sinfit3

#endif


/* demodulate a phase modulated series using the Hilbert transform */
ITERATE demodpm(s, fc, pdev, p0)
{
        local h, pm;

        if (argc < 4)
        {
                if (argc < 3)
                {
                        if (argc < 2)
                        {
                                if (argc < 1) error("demodpm - input series required");

                                /* estimate frequency */
                                fc = -1;
                        }
                        
                        pdev = pi / 2;
                }

                p0 = 0;
        }
        
        if (fc < 0)
        {
                /* estimate frequency */
                (fit, coef) = sinfit3(s);

                fc = coef[2];
        }

        /* hilbert transform */
        h  = hilb(s) * exp(-i * 2 * pi * (fc * xvals(s) + p0));

        /* unwrapped phase */
        pm = demodpm_zuphase(h);

        /* amplitude scaling */
        if (pdev > 0)
        {
                pm /= pdev;
        }

        /* restore the offset */
        setxoffset(pm, xoffset(s));

        return(pm);
}



static udmv = "";

/* unwrapped phase from complex values */
demodpm_zuphase(z, delay)
{
        local x, y, xty, dp, phi1, phi, phi1, f;

        if (argc < 2)
        {
                if (argc < 1) error("zuphase - input series required");
                
                delay = 0;
        }

        /* frequency values */
        f = isxy(z) ? xvals(z) : {};

        /* first phase value |phi1| <= pi */
        phi1 = (real(z[1]) == 0) ? 0 : atan2(imag(z[1]), real(z[1]));

        /* imag(z) / real(z) avoiding divide by 0 */
        z = tan(atan2(imag(z), real(z)));

        z[find(isnan(z))] = 0;

        /* z[i] and z[i-1] */
        y = extract(z, 1, length(z) - 1);
        x = extract(z, 2, -1);

        xty = x * y;

        /* force 1/0 to inf */
        udmv = getconf("use_default_math_value");
        setconf("use_default_math_value", "0");

        /* phase difference, from -pi/2 to pi/2 */
        dp = atan((x - y) / (1 + xty));

        /* integrate, first dp set to 0.0 */
        phi = extract(cumsum(dp), delay, -1, 0.0);

        if (not(isnan(phi1)))
        {
                phi += phi1;
        }


        /* handle DC phase value */
        phi0 = phi1;

        if (length(f) > 0)
        {
                if (f[1] == 0)
                {
                        phi[1] = phi0;
                }

                /* explicit frequency values, return XY */
                phi = xy(f, phi);
        }
        else
        {
                phi[1] = phi0;
        }

        setconf("use_default_math_value", udmv);
        udmv = "";

        if (outargc > 1)
        {
                /* separate components */
                return(phi, xvals(phi));
        }
        else
        {
                return(phi);
        }
}


/* error handler */
demodpm_zuphase_error(errnum, errmes)
{
        if (strlen(udmv) > 0)
        {
                setconf("use_default_math_value", udmv);
                udmv = "";
        }

        error(errmes);
}