Module musx.envs
Implements Env, a class for line (xy) envelopes and routines that operate on them.
Expand source code
###############################################################################
"""
Implements Env, a class for line (xy) envelopes and routines that operate
on them.
"""
__pdoc__ = {
    'Env.__new__': True
}
    
from .tools import rescale
class Env (tuple):
    """
    A subclass of tuple that represents line envelopes containing a series
    of x and y values: x1, y1, x2, y2, ..., xn, yn.
    """
    def __new__(self, xypairs):
        """
        Returns a new Env.
        Parameters
        ----------
        xypairs : list or tuple
            A list or tuple containing x y pairs x1, y1, x2, y2, ..., xn, yn
            where the x and y are ints or float and x values are
            in monotonically increasing order.
        """
        if not isinstance(xypairs, (list, tuple)):
            raise TypeError(f"not a list or tuple: {xypairs}.")
        if len(xypairs) < 2:
            raise ValueError(f"not a valid envelope list: {xypairs}.")
        if not len(xypairs) % 2 == 0:
            raise ValueError("Env(): odd number of envelope values {xy}")
        xx = None
        for x, y in zip(xypairs[0::2], xypairs[1::2]):
            if not isinstance(x, (int, float)):
                raise TypeError(f"Env(): x value {x} is not an int or float.")
            if not isinstance(y, (int, float)):
                raise TypeError(f"Env(): y value {y} is not an int or float.")
            if xx is None:
                xx = x
            elif x < xx:
                raise TypeError(f"Env(): x values {xx} and {x} not in increasing order.")
            xx = x
        return tuple.__new__(Env, xypairs)
    def pairs(self):
        """
        Returns an iterator producing the pairs of x,y values
        in the envelope.
        """
        return zip(self[0::2], self[1::2])
    def unzip(self):
        """
        Returns the envelope's x and y values as two separate lists.
        """
        return self[0::2], self[1::2]
        
    def interp(self, x, mode='lin'):
        """
        Returns the interpolated y value of x in the envelope. 
        
        Parameters
        ----------
        x : int | float
            The x value to interpolate in the envelope. If x
            is not in bounds then the first or last y value is returned.
        mode : string
            Specifies the type of interpolation performed; 'lin' is linear,
            'cos' is cosine, 'exp' is exponential and '-exp' is inverted
            exponential.
        
        Returns
        -------
        The interpolated value of x.
        """
        # iterate segments of the line function as pairs of points to 
        # find the segment that contains the x value. xr and yr are the
        # right side coords, and xl and yl are the left side coords.
        walk = self.pairs()
        xr,yr = next(walk)
        xl,yl = xr,yr
        for wx,wy in walk:
            # stop if right side xr is greater than x since lx < x < rx
            if xr > x:
                break
            xl,yl = xr,yr
            xr,yr = wx,wy
        return rescale(x, xl, xr, yl, yr, mode)
    @staticmethod
    def _interp(x, xys, mode):
        xr,yr = xys[0:2]
        xl,yl = xr,yr
        # iterate remaining pairs of x y values stepping by 2
        for wx,wy in zip(xys[2::2], xys[3::2]):
            if xr > x:
                break
            xl,yl = xr,yr
            xr,yr = wx,wy
        # print(x, xl, xr, yl, yr, mode)
        return rescale(x, xl, xr, yl, yr, mode)
    # def between(self, x, other, mode):
    #     """
    #     Returns a randomly selected y value between the y values of this and
    #     the other envelope.
    #     Parameters
    #     ----------
    #     x : int | float
    #         The x value to interpolate in both envelopes.
    #     other : Env
    #         The other envelope.
    #     mode : string
    #         The interpolation mode. See `interp()`.
        
    #     Returns
    #     -------
    #         A randomly interpolated value
    #     """
    #     pass
    def max(self, coord='y'):
        """
        Returns the largest value for coord, which defaults to 'y'.
        """
        pass
    def min(self, coord='y'):
        """
        Returns the smallest value for coord, which defaults to 'y'.
        """
        pass
    def inverted(self):
        """
        Returns a version of the envelope inverted along the y axis.
        """
        pass
    def reversed(self):
        """
        Returns a version of the envelope reversed along the x axis.
        """
        pass
    def normalized(self, axis='xy'):
        """
        Returns a version of the envelope normalized to lie between 0 and 1.
        If axis is 'xy' then both are normalized, otherwise specify 'x' or 'y'.
        Parameters
        ----------
        axis : 'xy' | 'x' | 'y'
            The axis to normalize.
        """
        pass
    def segments(self, num, mode='lin'):
        """
        Returns a list of y points defining num segments of the envelope
        The first and last y values always included in the list.
        """
        x0, x1 = self[0], self[-2]
        y0, y1 = self[1], self[-1]
        incr = (x1 - x0) / num 
        segs = []
        segs.append(y0)
        for n in range(1, num):
            segs.append(self.interp(incr * n, mode))
        segs.append(y1)
        return segs
def interp(x, *xys, mode='lin', mul=None, add=None):
    """
    A function that interpolates a y value for a given x in a
    series of x,y coordinate pairs. If x is not within bounds
    then the first or last y value is returned.
    Parameters
    ----------
    x : int | float
        The x value to interpolate in the sequence of x y values. 
    xys : series of int or float | list
        Either a series of in-line x, y values representing the envelope
        or a single list of x y coordinate pairs.
    mode : 'lin' | 'cos' | 'exp' | '-exp'
        A string that specifies the type of interpolation performed;
        'lin' is linear, 'cos' is cosine, 'exp' is exponential and 
        '-exp' is inverted exponential. The default is 'lin'. Note
        that if specified the value must provided as an explicit
        keyword arg, e.g. mode='cos'.
    mul : None | number
        A value to multiply the result by.
    add : None | number
        A value to add to the result after any multiplication.
    Returns
    -------
    The interpolated value of x.
    """
    if len(xys) == 1 and isinstance(xys[0], (tuple,list)):
        xys = xys[0]
    if not xys or len(xys) & 1:
        raise ValueError(f"coordinates not x y pairs: {xys}.")
    val = Env._interp(x, xys, mode)
    if mul:
        val *= mul
    if add:
        val += add
    return val
#def interp(x, *xys, **mode):
#    if isinstance(xys[0], (list, tuple)):
#        xys = xys[0] 
#    if len(xys) & 1:
#        raise ValueError(f"coordinates not in x y format: {coords}.")
#    mode = mode['mode'] if mode else 'lin'
#    return Env._interp(x, xys, mode)
if __name__ == '__main__':
    env = Env([0, 0, 100, 1])
    print(env.segments(1, 'lin'))
    # print("Testing env")
    # env = Env([0, 0 , 50, 1, 100, 0])
    # print(env)
    # print(env.pairs())
    # for x,y in env.pairs():
    #     print("x=", x,"y=", y)
    # print(env.interp(49))
    import matplotlib.pyplot as plt
    data = [0,0,1,1]  #[0, 0, 0.2, 0.5, 0.5, 0.3, 1, 1]
    data = [0,34, 1,13, 2,18, 3,12, 4,38, 5,0, 6,25, 7,7]
    env = Env(data)
    l1 = [env.interp(x) for x in frange(0,7,.25)]
    l2 = [interp(x, *data) for x in frange(0,7,.25)]
    assert l1 == l2
    # px, py = env.unzip()
    # print("lists:" , px,py)
    # px, py = [],[]
    # for x in frange(0,7,.1):
    #     px.append(x)
    #     py.append(env.interp(x, '-exp'))
    px, py = [x for x in range(50)], env.segments(50-1, 'cos')
    print('len px=', len(px), "len py=", len(py))
    # plt.plot(px, py)
    # plt.show()
Functions
def interp(x, *xys, mode='lin', mul=None, add=None)- 
A function that interpolates a y value for a given x in a series of x,y coordinate pairs. If x is not within bounds then the first or last y value is returned.
Parameters
x:int | float- The x value to interpolate in the sequence of x y values.
 xys:seriesofintorfloat | list- Either a series of in-line x, y values representing the envelope or a single list of x y coordinate pairs.
 mode:'lin' | 'cos' | 'exp' | '-exp'- A string that specifies the type of interpolation performed; 'lin' is linear, 'cos' is cosine, 'exp' is exponential and '-exp' is inverted exponential. The default is 'lin'. Note that if specified the value must provided as an explicit keyword arg, e.g. mode='cos'.
 mul:None | number- A value to multiply the result by.
 add:None | number- A value to add to the result after any multiplication.
 
Returns
The interpolated value of x.
Expand source code
def interp(x, *xys, mode='lin', mul=None, add=None): """ A function that interpolates a y value for a given x in a series of x,y coordinate pairs. If x is not within bounds then the first or last y value is returned. Parameters ---------- x : int | float The x value to interpolate in the sequence of x y values. xys : series of int or float | list Either a series of in-line x, y values representing the envelope or a single list of x y coordinate pairs. mode : 'lin' | 'cos' | 'exp' | '-exp' A string that specifies the type of interpolation performed; 'lin' is linear, 'cos' is cosine, 'exp' is exponential and '-exp' is inverted exponential. The default is 'lin'. Note that if specified the value must provided as an explicit keyword arg, e.g. mode='cos'. mul : None | number A value to multiply the result by. add : None | number A value to add to the result after any multiplication. Returns ------- The interpolated value of x. """ if len(xys) == 1 and isinstance(xys[0], (tuple,list)): xys = xys[0] if not xys or len(xys) & 1: raise ValueError(f"coordinates not x y pairs: {xys}.") val = Env._interp(x, xys, mode) if mul: val *= mul if add: val += add return val 
Classes
class Env (xypairs)- 
A subclass of tuple that represents line envelopes containing a series of x and y values: x1, y1, x2, y2, …, xn, yn.
Expand source code
class Env (tuple): """ A subclass of tuple that represents line envelopes containing a series of x and y values: x1, y1, x2, y2, ..., xn, yn. """ def __new__(self, xypairs): """ Returns a new Env. Parameters ---------- xypairs : list or tuple A list or tuple containing x y pairs x1, y1, x2, y2, ..., xn, yn where the x and y are ints or float and x values are in monotonically increasing order. """ if not isinstance(xypairs, (list, tuple)): raise TypeError(f"not a list or tuple: {xypairs}.") if len(xypairs) < 2: raise ValueError(f"not a valid envelope list: {xypairs}.") if not len(xypairs) % 2 == 0: raise ValueError("Env(): odd number of envelope values {xy}") xx = None for x, y in zip(xypairs[0::2], xypairs[1::2]): if not isinstance(x, (int, float)): raise TypeError(f"Env(): x value {x} is not an int or float.") if not isinstance(y, (int, float)): raise TypeError(f"Env(): y value {y} is not an int or float.") if xx is None: xx = x elif x < xx: raise TypeError(f"Env(): x values {xx} and {x} not in increasing order.") xx = x return tuple.__new__(Env, xypairs) def pairs(self): """ Returns an iterator producing the pairs of x,y values in the envelope. """ return zip(self[0::2], self[1::2]) def unzip(self): """ Returns the envelope's x and y values as two separate lists. """ return self[0::2], self[1::2] def interp(self, x, mode='lin'): """ Returns the interpolated y value of x in the envelope. Parameters ---------- x : int | float The x value to interpolate in the envelope. If x is not in bounds then the first or last y value is returned. mode : string Specifies the type of interpolation performed; 'lin' is linear, 'cos' is cosine, 'exp' is exponential and '-exp' is inverted exponential. Returns ------- The interpolated value of x. """ # iterate segments of the line function as pairs of points to # find the segment that contains the x value. xr and yr are the # right side coords, and xl and yl are the left side coords. walk = self.pairs() xr,yr = next(walk) xl,yl = xr,yr for wx,wy in walk: # stop if right side xr is greater than x since lx < x < rx if xr > x: break xl,yl = xr,yr xr,yr = wx,wy return rescale(x, xl, xr, yl, yr, mode) @staticmethod def _interp(x, xys, mode): xr,yr = xys[0:2] xl,yl = xr,yr # iterate remaining pairs of x y values stepping by 2 for wx,wy in zip(xys[2::2], xys[3::2]): if xr > x: break xl,yl = xr,yr xr,yr = wx,wy # print(x, xl, xr, yl, yr, mode) return rescale(x, xl, xr, yl, yr, mode) # def between(self, x, other, mode): # """ # Returns a randomly selected y value between the y values of this and # the other envelope. # Parameters # ---------- # x : int | float # The x value to interpolate in both envelopes. # other : Env # The other envelope. # mode : string # The interpolation mode. See `interp()`. # Returns # ------- # A randomly interpolated value # """ # pass def max(self, coord='y'): """ Returns the largest value for coord, which defaults to 'y'. """ pass def min(self, coord='y'): """ Returns the smallest value for coord, which defaults to 'y'. """ pass def inverted(self): """ Returns a version of the envelope inverted along the y axis. """ pass def reversed(self): """ Returns a version of the envelope reversed along the x axis. """ pass def normalized(self, axis='xy'): """ Returns a version of the envelope normalized to lie between 0 and 1. If axis is 'xy' then both are normalized, otherwise specify 'x' or 'y'. Parameters ---------- axis : 'xy' | 'x' | 'y' The axis to normalize. """ pass def segments(self, num, mode='lin'): """ Returns a list of y points defining num segments of the envelope The first and last y values always included in the list. """ x0, x1 = self[0], self[-2] y0, y1 = self[1], self[-1] incr = (x1 - x0) / num segs = [] segs.append(y0) for n in range(1, num): segs.append(self.interp(incr * n, mode)) segs.append(y1) return segsAncestors
- builtins.tuple
 
Static methods
def __new__(self, xypairs)- 
Returns a new Env.
Parameters
xypairs:listortuple- A list or tuple containing x y pairs x1, y1, x2, y2, …, xn, yn where the x and y are ints or float and x values are in monotonically increasing order.
 
Expand source code
def __new__(self, xypairs): """ Returns a new Env. Parameters ---------- xypairs : list or tuple A list or tuple containing x y pairs x1, y1, x2, y2, ..., xn, yn where the x and y are ints or float and x values are in monotonically increasing order. """ if not isinstance(xypairs, (list, tuple)): raise TypeError(f"not a list or tuple: {xypairs}.") if len(xypairs) < 2: raise ValueError(f"not a valid envelope list: {xypairs}.") if not len(xypairs) % 2 == 0: raise ValueError("Env(): odd number of envelope values {xy}") xx = None for x, y in zip(xypairs[0::2], xypairs[1::2]): if not isinstance(x, (int, float)): raise TypeError(f"Env(): x value {x} is not an int or float.") if not isinstance(y, (int, float)): raise TypeError(f"Env(): y value {y} is not an int or float.") if xx is None: xx = x elif x < xx: raise TypeError(f"Env(): x values {xx} and {x} not in increasing order.") xx = x return tuple.__new__(Env, xypairs) 
Methods
def interp(self, x, mode='lin')- 
Returns the interpolated y value of x in the envelope.
Parameters
x:int | float- The x value to interpolate in the envelope. If x is not in bounds then the first or last y value is returned.
 mode:string- Specifies the type of interpolation performed; 'lin' is linear, 'cos' is cosine, 'exp' is exponential and '-exp' is inverted exponential.
 
Returns
The interpolated value of x.
Expand source code
def interp(self, x, mode='lin'): """ Returns the interpolated y value of x in the envelope. Parameters ---------- x : int | float The x value to interpolate in the envelope. If x is not in bounds then the first or last y value is returned. mode : string Specifies the type of interpolation performed; 'lin' is linear, 'cos' is cosine, 'exp' is exponential and '-exp' is inverted exponential. Returns ------- The interpolated value of x. """ # iterate segments of the line function as pairs of points to # find the segment that contains the x value. xr and yr are the # right side coords, and xl and yl are the left side coords. walk = self.pairs() xr,yr = next(walk) xl,yl = xr,yr for wx,wy in walk: # stop if right side xr is greater than x since lx < x < rx if xr > x: break xl,yl = xr,yr xr,yr = wx,wy return rescale(x, xl, xr, yl, yr, mode) def inverted(self)- 
Returns a version of the envelope inverted along the y axis.
Expand source code
def inverted(self): """ Returns a version of the envelope inverted along the y axis. """ pass def max(self, coord='y')- 
Returns the largest value for coord, which defaults to 'y'.
Expand source code
def max(self, coord='y'): """ Returns the largest value for coord, which defaults to 'y'. """ pass def min(self, coord='y')- 
Returns the smallest value for coord, which defaults to 'y'.
Expand source code
def min(self, coord='y'): """ Returns the smallest value for coord, which defaults to 'y'. """ pass def normalized(self, axis='xy')- 
Returns a version of the envelope normalized to lie between 0 and 1.
If axis is 'xy' then both are normalized, otherwise specify 'x' or 'y'.
Parameters
axis:'xy' | 'x' | 'y'- The axis to normalize.
 
Expand source code
def normalized(self, axis='xy'): """ Returns a version of the envelope normalized to lie between 0 and 1. If axis is 'xy' then both are normalized, otherwise specify 'x' or 'y'. Parameters ---------- axis : 'xy' | 'x' | 'y' The axis to normalize. """ pass def pairs(self)- 
Returns an iterator producing the pairs of x,y values in the envelope.
Expand source code
def pairs(self): """ Returns an iterator producing the pairs of x,y values in the envelope. """ return zip(self[0::2], self[1::2]) def reversed(self)- 
Returns a version of the envelope reversed along the x axis.
Expand source code
def reversed(self): """ Returns a version of the envelope reversed along the x axis. """ pass def segments(self, num, mode='lin')- 
Returns a list of y points defining num segments of the envelope The first and last y values always included in the list.
Expand source code
def segments(self, num, mode='lin'): """ Returns a list of y points defining num segments of the envelope The first and last y values always included in the list. """ x0, x1 = self[0], self[-2] y0, y1 = self[1], self[-1] incr = (x1 - x0) / num segs = [] segs.append(y0) for n in range(1, num): segs.append(self.interp(incr * n, mode)) segs.append(y1) return segs def unzip(self)- 
Returns the envelope's x and y values as two separate lists.
Expand source code
def unzip(self): """ Returns the envelope's x and y values as two separate lists. """ return self[0::2], self[1::2]