View Raw SPL
/*****************************************************************************
*                                                                            *
*   LEVELDURATION.SPL  Copyright (C) 2023 DSP Development Corporation        *
*                               All Rights Reserved                          *
*                                                                            *
*   Author:      Randy Race                                                  *
*                                                                            *
*   Synopsis:    Returns XY series where a level is crossed for a duration   *
*                                                                            *
*   Revisions:    7 Jun 2023  RRR  Creation                                  *
*                                                                            *
*****************************************************************************/

#include 

#if @HELP_LEVELDURATION

    LEVELDURATION

    Purpose: Returns an XY series of the locations where a series crosses
             a level and remains above the level for a specified duration.

    Syntax:  LEVELDURATION(series, level, duration, edgeout, tol)

             (r, f) = LEVELDURATION(series, level, duration, edgeout, tol)

               series  - A series, the input series.

                level  - Optional. A real, the rising and falling
                         crossing threshold. Defaults to 0.0.

              duration - Optional. A real, the time duration required
                         between crossings.

               edgeout - Optional. An integer, the output value alignment.
                         The output value will be placed to the left or right
                         of the actual crossing point as specified below:

                          0: Left if input edge rising, right if falling
                             (default).
  
                          1: Right on rising, left on falling.
    
                          2: Right whether rising or falling.
         
                          3: Left whether rising or falling.
        
                          4: Linearly interpolate the X crossing values if
                             necessary.

                   tol - Optional. A real, the level crossing tolerance.
                         Defaults to EPS.


    Returns: An XY series where the X values are the rising edges of where
             the series remains above LEVEL for DURATION. The Y values are
             the same as LEVEL.

             (r, f) = LEVELDURATION(series, level, duration, edgeout, tol)
             returns two XY series, the rising (starting) edges and the falling
             (ending) edges.

    Example:
             W1: gsin(10000, 1/1000, 0.3)
             W2: levelduration(w1)
             W3: W1;overp(w2,lred);setplotstyle(1,2);setsym(8, 2)

             W1 contains the source series.

             W2 returns a 3 point XY series of the rising edges
             where W1 crosses 0.0 and remains above 0.0 for 1 second.

             W3 displays the original series with the rising edges
             overplotted as red up arrows.

    Example:
             W1: gsin(10000, 1/1000, 0.3)
             W2: levelduration(w1, 0.0, 1.0)
             W3: W1;overp(w2,lred);setplotstyle(1,2);setsym(8, 2)

             Same as above except the LEVEL and DURATION parameters are
             explicit.

    Example:
             W1: gsin(10000, 1/1000, 0.3)
             W2: (r, f) = levelduration(w1,0.5,1.0);r;overplot(f,lgreen);setsym(8, 1);setsym(9,2)
             W3: W1;overp(r,lred);overp(f,lgreen);setplotstyle(1,2);setsym(8,2);setplotstyle(1,3);setsym(9,3)

             W1 contains the source series.

             W2 returns two 3 point XY series where R contains the rising
             (beginning) edges and F contains the falling (ending) edges
             where W1 crosses 0.5 and remains above 0.5 for 1 second.

             W3 displays the original series with the rising edges
             overplotted as red up arrows and the falling edges are
             overplotted as green down arrows.

    Example:
             W1: gsweep(10000, 1/10000, 1, 10.3)
             W2: (r, f) = levelduration(w1,0.5,0.04);r;overplot(f,lgreen);setsym(8, 1);setsym(9,2)
             W3: W1;overp(r,lred);overp(f,lgreen);setplotstyle(1,2);setsym(8,2);setplotstyle(1,3);setsym(9,3)

             W1 contains a swept sinewave from 1 Hz to 10.3 Hz.

             W2 returns two 3 point XY series where R contains the rising
             (beginning) edges and F contains the falling (ending) edges
             where W1 crosses 0.5 and remains above 0.5 for 0.04 seconds.

             W3 displays the original series with the rising edges
             overplotted as red up arrows and the falling edges are
             overplotted as green down arrows. The plot shows 4 intervals
             meet the condition of crossing 0.5 for at least 0.04 seconds.
             Note the resulting rising edges are to the left of the actual
             crossing point and the falling edges are to the right of the
             crossing point.

    Example:
             W1: gsweep(10000, 1/10000, 1.0, 10.3)
             W2: (r, f) = levelduration(w1,0.5,0.04,4);r;overplot(f,lgreen);setsym(8, 1);setsym(9,2)
             W3: W1;overp(r,lred);overp(f,lgreen);setplotstyle(1,2);setsym(8,2);setplotstyle(1,3);setsym(9,3)

             Same as above except the rising and crossing edges are linearly
             interpolated to the actual crossing location.

    Example:
             W1: gsweep(10000, 1/10000, 1.0, 10.3)
             W2: (r, f) = levelduration(w1,0.5,0.04,4);r;overplot(f,lgreen);setsym(8, 1);setsym(9,2)
             W3: W1;overp(r,lred);overp(f,lgreen);setplotstyle(1,2);setsym(8,2);setplotstyle(1,3);setsym(9,3)
             W4: integ(gnorm(1000, 1/1000))
             W5: xy(xvals(r), xylookup(W4, xvals(r)));points;setsym(8)
             W6: xy(xvals(f), xylookup(W4, xvals(f)));points;setsym(9)
             w7: W4;overp(w5, lred);overp(w6, lgreen)

             Same as above except the rising and crossing edges are used to
             mark the same locations of an associated series in W4.

             W7 shows the rising and falling edges overplotted on the series
             in W4 showing the four intervals of W1 that cross 0.5 for
             at least 0.04 seconds. Note the source series in W1 and the
             associated series in W4 have different sample rates.

    Remarks:
             LEVELDURATION returns an XY series where the input crosses
             LEVEL and remains above LEVEL for DURATION. The Y values of
             the returned XY series are set to LEVEL.

             The EDGEOUT parameter determines the precise location of
             the level crossing. By default, if the input series crosses
             LEVEL between samples, the rising edge is the sample to the
             left of the crossing and the falling edge is the sample to the
             right of the crossing. Set EDGEOUT to 4 to linearly interpolate
             the crossing locations. See LEVELCROSS for more details.

             TOL is used to handle cases where the series reaches LEVEL
             exactly but does not cross it. The default value for TOL
             is EPS("double").

    See Also:
             Levelcross
             Xylookup
#endif


/* find locations that cross level and remain above level for duration */
levelduration(s, level, duration, edgeout, tol)
{
        local xr, xf, idx, xyxr = {}, xyxf = {};

        if (argc < 5)
        {
                if (argc < 4)
                {
                        if (argc < 3)
                        {
                                if (argc < 2)
                                {
                                        if (argc < 1) error(sprintf("%s - input series required", __FUNC__));

                                        level = 0.0;
                                }

                                duration = 1.0;
                        }

                        edgeout = 0;
                }

                tol = eps();
        }

        if (edgeout == 4)
        {
                /* rising x locations, interpolated */
                xr = xvals(levelcross(s, level + tol, 1, 4));

                /* falling x locations, interpolated */
                xf = xvals(levelcross(s, level + tol, 2, 4));
        }
        else
        {
                /* rising x locations, non-interpolated */
                lev = levelcross(s, level + tol, 1, edgeout);
                xr  = xvals(lev);
                xr  = xr[find(lev == 1)];

                /* falling x locations, non-interpolated */
                lev = levelcross(s, level + tol, 2, edgeout);
                xf  = xvals(lev);
                xf  = xf[find(lev == 1)];
        }

        if (length(xf) > 0 && length(xr) > 0)
        {
                /* force first falling x after first rising x */
                if (xf[1] < xr[1])
                {
                        xf = extract(xf, 2, length(xr));
                }

                /* indices of X that meet or exceed duration */
                idx = find((xf - xr) >= duration);

                /* rising series */
                xyxr = xy(xr[idx], ones(length(idx), 1) * level);

                /* falling series */
                xyxf = xy(xf[idx], ones(length(idx), 1) * level);
        }

        return(xyxr, xyxf);
}