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
:series
ofint
orfloat | 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 segs
Ancestors
- builtins.tuple
Static methods
def __new__(self, xypairs)
-
Returns a new Env.
Parameters
xypairs
:list
ortuple
- 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]