audiolazy/lazy_filters.py
class CascadeFilter(FilterList):
  """
  Filter cascade as a list of filters.

  Note
  ----
  A filter is any callable that receives an iterable as input and returns a
  Stream.

  Examples
  --------
  >>> filt = CascadeFilter(z ** -1, 2 * (1 - z ** -3))
  >>> data = Stream(1, 3, 5, 3, 1, -1, -3, -5, -3, -1) # Endless
  >>> filt(data, zero=0).take(15)
  [0, 2, 6, 10, 4, -4, -12, -12, -12, -4, 4, 12, 12, 12, 4]

  """
  def __call__(self, *args, **kwargs):
    return reduce(lambda data, filt: filt(data, *args[1:], **kwargs),
                  self.callables, args[0])

  @property
  def numpoly(self):
    try:
      return reduce(operator.mul, (filt.numpoly for filt in self.callables))
    except AttributeError:
      raise AttributeError("Non-linear filter")

  @property
  def denpoly(self):
    try:
      return reduce(operator.mul, (filt.denpoly for filt in self.callables))
    except AttributeError:
      raise AttributeError("Non-linear filter")

  @elementwise("freq", 1)
  def freq_response(self, freq):
    return reduce(operator.mul, (filt.freq_response(freq)
                                 for filt in self.callables))

  @property
  def poles(self):
    if not self.is_lti():
      raise AttributeError("Not a LTI filter")
    return reduce(operator.concat, (filt.poles for filt in self.callables))

  @property
  def zeros(self):
    if not self.is_lti():
      raise AttributeError("Not a LTI filter")
    return reduce(operator.concat, (filt.zeros for filt in self.callables))
audiolazy/lazy_auditory.py
@erb.strategy("gm90", "glasberg_moore_90", "glasberg_moore")
@elementwise("freq", 0)
@format_docstring(erb._doc_template, authors="Glasberg and Moore", year=1990)
def erb(freq, Hz=None):
  """
    ``B. R. Glasberg and B. C. J. Moore, "Derivation of auditory filter
    shapes from notched-noise data". Hearing Research, vol. 47, 1990, pp.
    103-108.``
  """
  if Hz is None:
    if freq < 7: # Perhaps user tried something up to 2 * pi
      raise ValueError("Frequency out of range.")
    Hz = 1
  fHz = freq / Hz
  result = 24.7 * (4.37e-3 * fHz + 1.)
  return result * Hz
audiolazy/lazy_auditory.py
def gammatone_erb_constants(n):
  """
  Constants for using the real bandwidth in the gammatone filter, given its
  order. Returns a pair :math:`(x, y) = (1/a_n, c_n)`.

  Based on equations from:

    ``Holdsworth, J.; Patterson, R.; Nimmo-Smith, I.; Rice, P. Implementing a
    GammaTone Filter Bank. In: SVOS Final Report, Annex C, Part A: The
    Auditory Filter Bank. 1988.``

  First returned value is a bandwidth compensation for direct use in the
  gammatone formula:

  >>> x, y = gammatone_erb_constants(4)
  >>> central_frequency = 1000
  >>> round(x, 3)
  1.019
  >>> bandwidth = x * erb["moore_glasberg_83"](central_frequency)
  >>> round(bandwidth, 2)
  130.52

  Second returned value helps us find the ``3 dB`` bandwidth as:

  >>> x, y = gammatone_erb_constants(4)
  >>> central_frequency = 1000
  >>> bandwidth3dB = x * y * erb["moore_glasberg_83"](central_frequency)
  >>> round(bandwidth3dB, 2)
  113.55

  """
  tnt = 2 * n - 2
  return (factorial(n - 1) ** 2 / (pi * factorial(tnt) * 2 ** -tnt),
          2 * (2 ** (1. / n) - 1) ** .5
         )
audiolazy/lazy_misc.py
@almost_eq.strategy("bits")
def almost_eq(a, b, bits=32, tol=1, ignore_type=True, pad=0.):
  """
  Almost equal, based on the amount of floating point significand bits.

  Alternative to "a == b" for float numbers and iterables with float numbers,
  and tests for sequence contents (i.e., an elementwise a == b, that also
  works with generators, nested lists, nested generators, etc.). If the type
  of both the contents and the containers should be tested too, set the
  ignore_type keyword arg to False.
  Default version is based on 32 bits IEEE 754 format (23 bits significand).
  Could use 64 bits (52 bits significand) but needs a
  native float type with at least that size in bits.
  If a and b sizes differ, at least one will be padded with the pad input
  value to keep going with the comparison.

  Note
  ----
  Be careful with endless generators!

  """
  if not (ignore_type or type(a) == type(b)):
    return False
  is_it_a = isinstance(a, Iterable)
  is_it_b = isinstance(b, Iterable)
  if is_it_a != is_it_b:
    return False
  if is_it_a:
    return all(almost_eq.bits(ai, bi, bits, tol, ignore_type)
               for ai, bi in xzip_longest(a, b, fillvalue=pad))
  significand = {32: 23, 64: 52, 80: 63, 128: 112
                }[bits] # That doesn't include the sign bit
  power = tol - significand - 1
  return abs(a - b) <= 2 ** power * abs(a + b)
audiolazy/lazy_compat.py
def iteritems(dictionary):
  """
  Function to use the generator-based items iterator over built-in
  dictionaries in both Python 2 and 3.
  """
  try:
    return getattr(dictionary, "iteritems")()
  except AttributeError:
    return iter(getattr(dictionary, "items")())
audiolazy/lazy_math.py
def log(x, base=None):
def log1p(x):
def log10(x):
def log2(x):
def factorial(n):
def dB10(data):
def dB20(data):
def sign(x):
audiolazy/lazy_auditory.py
@gammatone.strategy("sampled")
@format_docstring(gammatone._doc_template, model="a sampled impulse response",
  extra_params="\n  ".join([
    "phase :", "  Phase, in radians. Defaults to zero (cosine)."
    "eta :", "  Gammatone filter order. Defaults to 4.", "" # Skip a line
  ]),
)
def gammatone(freq, bandwidth, phase=0, eta=4):
  """
    ``Bellini, D. J. S. "AudioLazy: Processamento digital de sinais
    expressivo e em tempo real", IME-USP, Mastership Thesis, 2013.``

  This implementation have the impulse response (for each sample ``n``,
  keeping the input parameter names):

  .. math::

    n^{{eta - 1}} e^{{- bandwidth \cdot n}} \cos(freq \cdot n + phase)
  """
  assert eta >= 1

  A = exp(-bandwidth)
  numerator = cos(phase) - A * cos(freq - phase) * z ** -1
  denominator = 1 - 2 * A * cos(freq) * z ** -1 + A ** 2 * z ** -2
  filt = (numerator / denominator).diff(n=eta-1, mul_after=-z)

  # Filter is done, but the denominator might have some numeric loss
  f0 = ZFilter(filt.numpoly) / denominator
  f0 /= abs(f0.freq_response(freq)) # Max gain == 1.0 (0 dB)
  fn = 1 / denominator
  fn /= abs(fn.freq_response(freq))
  return CascadeFilter([f0] + [fn] * (eta - 1))
audiolazy/lazy_auditory.py
@phon2dB.strategy("iso226", "iso226_2003", "iso_fdis_226_2003")
def phon2dB(loudness=None):
  """
  Loudness in phons to Sound Pressure Level (SPL) in dB using the
  ISO/FDIS 226:2003 model.

  This function needs Scipy, as ``scipy.interpolate.UnivariateSpline``
  objects are used as interpolators.

  Parameters
  ----------
  loudness :
    The loudness value in phons to be converted, or None (default) to get
    the threshold of hearing.

  Returns
  -------
  A callable that returns the SPL dB value for each given frequency in hertz.

  Note
  ----
  See ``phon2dB.iso226.schema`` and ``phon2dB.iso226.table`` to know the
  original frequency used for the result. The result for any other value is
  an interpolation (spline). Don't trust on values nor lower nor higher than
  the frequency limits there (20Hz and 12.5kHz) as they're not part of
  ISO226 and no value was collected to estimate them (they're just a spline
  interpolation to reach 1000dB at -30Hz and 32kHz). Likewise, the trustful
  loudness input range is from 20 to 90 phon, as written on ISO226, although
  other values aren't found by a spline interpolation but by using the
  formula on section 4.1 of ISO226.

  Hint
  ----
  The ``phon2dB.iso226.table`` also have other useful information, such as
  the threshold values in SPL dB.

  """
  from scipy.interpolate import UnivariateSpline

  table = phon2dB.iso226.table
  schema = phon2dB.iso226.schema
  freqs = [row[schema.index("freq")] for row in table]

  if loudness is None: # Threshold levels
    spl = [row[schema.index("threshold")] for row in table]

  else: # Curve for a specific phon value
    def get_pressure_level(freq, alpha, loudness_base, threshold):
      return 10 / alpha * math.log10(
        4.47e-3 * (10 ** (.025 * loudness) - 1.14) +
        (.4 * 10 ** ((threshold + loudness_base) / 10 - 9)) ** alpha
      ) - loudness_base + 94

    spl = [get_pressure_level(**dict(xzip(schema, args))) for args in table]

  interpolator = UnivariateSpline(freqs, spl, s=0)
  interpolator_low = UnivariateSpline([-30] + freqs, [1e3] + spl, s=0)
  interpolator_high = UnivariateSpline(freqs + [32000], spl + [1e3], s=0)

  @elementwise("freq", 0)
  def freq2dB_spl(freq):
    if freq < 20:
      return interpolator_low(freq).tolist()
    if freq > 12500:
      return interpolator_high(freq).tolist()
    return interpolator(freq).tolist()
  return freq2dB_spl
audiolazy/lazy_misc.py
def sHz(rate):
  """
  Unit conversion constants.

  Useful for casting to/from the default package units (number of samples for
  time and rad/second for frequency). You can use expressions like
  ``440 * Hz`` to get a frequency value, or assign like ``kHz = 1e3 * Hz`` to
  get other unit, as you wish.

  Parameters
  ----------
  rate :
    Sample rate in samples per second

  Returns
  -------
  A tuple ``(s, Hz)``, where ``s`` is the second unit and ``Hz`` is the hertz
  unit, as the number of samples and radians per sample, respectively.

  """
  return float(rate), 2 * pi / rate
audiolazy/lazy_stream.py
class Stream(meta(Iterable, metaclass=StreamMeta)):
  """
  Stream class. Stream instances are iterables that can be seem as generators
  with elementwise operators.

  Examples
  --------
  If you want something like:

  >>> import itertools
  >>> x = itertools.count()
  >>> y = itertools.repeat(3)
  >>> z = 2*x + y
  Traceback (most recent call last):
      ...
  TypeError: unsupported operand type(s) for *: 'int' and ...

  That won't work with standard itertools. That's an error, and not only
  __mul__ but also __add__ isn't supported by their types. On the other hand,
  you can use this Stream class:

  >>> x = Stream(itertools.count()) # Iterable
  >>> y = Stream(3) # Non-iterable repeats endlessly
  >>> z = 2*x + y
  >>> z
  <audiolazy.lazy_stream.Stream object at 0x...>
  >>> z.take(12)
  [3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25]

  If you just want to use your existing code, an "itertools" alternative is
  already done to help you:

  >>> from audiolazy import lazy_itertools as itertools
  >>> x = itertools.count()
  >>> y = itertools.repeat(3)
  >>> z = 2*x + y
  >>> w = itertools.takewhile(lambda pair: pair[0] < 10, enumerate(z))
  >>> list(el for idx, el in w)
  [3, 5, 7, 9, 11, 13, 15, 17, 19, 21]

  All operations over Stream objects are lazy and not thread-safe.

  See Also
  --------
  thub :
    "Tee" hub to help using the Streams like numbers in equations and filters.
  tee :
    Just like itertools.tee, but returns a tuple of Stream instances.
  Stream.tee :
    Keeps the Stream usable and returns a copy to be used safely.
  Stream.copy :
    Same to ``Stream.tee``.

  Notes
  -----
  In that example, after declaring z as function of x and y, you should
  not use x and y anymore. Use the thub() or the tee() functions, or
  perhaps the x.tee() or x.copy() Stream methods instead, if you need
  to use x again otherwhere.

  """
  __ignored_classes__ = tuple()

  def __init__(self, *dargs):
    """
    Constructor for a Stream.

    Parameters
    ----------
    *dargs:
      The parameters should be iterables that will be chained together. If
      they're not iterables, the stream will be an endless repeat of the
      given elements. If any parameter is a generator and its contents is
      used elsewhere, you should use the "tee" (Stream method or itertools
      function) before.

    Notes
    -----
    All operations that works on the elements will work with this iterator
    in a element-wise fashion (like Numpy 1D arrays). When the stream
    sizes differ, the resulting stream have the size of the shortest
    operand.

    Examples
    --------
    A finite sequence:

    >>> x = Stream([1,2,3]) + Stream([8,5]) # Finite constructor
    >>> x
    <audiolazy.lazy_stream.Stream object at 0x...>
    >>> tuple(x)
    (9, 7)

    But be careful:

    >>> x = Stream(1,2,3) + Stream(8,5) # Periodic constructor
    >>> x
    <audiolazy.lazy_stream.Stream object at 0x...>
    >>> x.take(15) # Don't try "tuple" or "list": this Stream is endless!
    [9, 7, 11, 6, 10, 8, 9, 7, 11, 6, 10, 8, 9, 7, 11]

    """
    if len(dargs) == 0:
      raise TypeError("Missing argument(s)")

    elif len(dargs) == 1:
      if isinstance(dargs[0], Iterable):
        self._data = iter(dargs[0])
      else:
        self._data = it.repeat(dargs[0])

    else:
      if all(isinstance(arg, Iterable) for arg in dargs):
        self._data = it.chain(*dargs)
      elif not any(isinstance(arg, Iterable) for arg in dargs):
        self._data = it.cycle(dargs)
      else:
        raise TypeError("Input with both iterables and non-iterables")

  def __iter__(self):
    """ Returns the Stream contents iterator. """
    return self._data

  def __bool__(self):
    """
    Boolean value of a stream, called by the bool() built-in and by "if"
    tests. As boolean operators "and", "or" and "not" couldn't be overloaded,
    any trial to cast an instance of this class to a boolean should be seen
    as a mistake.
    """
    raise TypeError("Streams can't be used as booleans.\n"
                    "If you need a boolean stream, try using bitwise "
                    "operators & and | instead of 'and' and 'or'. If using "
                    "'not', you can use the inversion operator ~, casting "
                    "its returned int back to bool.\n"
                    "If you're using it in a 'if' comparison (e.g. for unit "
                    "testing), try to freeze the stream before with "
                    "list(my_stream) or tuple(my_stream).")

  __nonzero__ = __bool__ # For Python 2.x compatibility

  def blocks(self, *args, **kwargs):
    """
    Interface to apply audiolazy.blocks directly in a stream, returning
    another stream. Use keyword args.
    """
    return Stream(blocks(iter(self), *args, **kwargs))

  def take(self, n=None, constructor=list):
    """
    Returns a container with the n first elements from the Stream, or less if
    there aren't enough. Use this without args if you need only one element
    outside a list.

    Parameters
    ----------
    n :
      Number of elements to be taken. Defaults to None.
      Rounded when it's a float, and this can be ``inf`` for taking all.
    constructor :
      Container constructor function that can receie a generator as input.
      Defaults to ``list``.

    Returns
    -------
    The first ``n`` elements of the Stream sequence, created by the given
    constructor unless ``n == None``, which means returns the next element
    from the sequence outside any container.
    If ``n`` is None, this can raise StopIteration due to lack of data in
    the Stream. When ``n`` is a number, there's no such exception.

    Examples
    --------
    >>> Stream(5).take(3) # Three elements
    [5, 5, 5]
    >>> Stream(1.2, 2, 3).take() # One element, outside a container
    1.2
    >>> Stream(1.2, 2, 3).take(1) # With n = 1 argument, it'll be in a list
    [1.2]
    >>> Stream(1.2, 2, 3).take(1, constructor=tuple) # Why not a tuple?
    (1.2,)
    >>> Stream([1, 2]).take(3) # More than the Stream size, n is integer
    [1, 2]
    >>> Stream([]).take() # More than the Stream size, n is None
    Traceback (most recent call last):
      ...
    StopIteration

    Taking rounded float quantities and "up to infinity" elements
    (don't try using ``inf`` with endless Stream instances):

    >>> Stream([4, 3, 2, 3, 2]).take(3.4)
    [4, 3, 2]
    >>> Stream([4, 3, 2, 3, 2]).take(3.6)
    [4, 3, 2, 3]
    >>> Stream([4, 3, 2, 3, 2]).take(inf)
    [4, 3, 2, 3, 2]

    See Also
    --------
    Stream.peek :
      Returns the n first elements from the Stream, without removing them.

    Note
    ----
    You should avoid using take() as if this would be an iterator. Streams
    are iterables that can be easily part of a "for" loop, and their
    iterators (the ones automatically used in for loops) are slightly faster.
    Use iter() builtin if you need that, instead, or perhaps the blocks
    method.

    """
    if n is None:
      return next(self._data)
    if isinf(n) and n > 0:
      return constructor(self._data)
    if isinstance(n, float):
      n = rint(n) if n > 0 else 0 # So this works with -inf and nan
    return constructor(next(self._data) for _ in xrange(n))

  def copy(self):
    """
    Returns a "T" (tee) copy of the given stream, allowing the calling
    stream to continue being used.
    """
    a, b = it.tee(self._data) # 2 generators, not thread-safe
    self._data = a
    return Stream(b)

  def peek(self, n=None, constructor=list):
    """
    Sees/peeks the next few items in the Stream, without removing them.

    Besides that this functions keeps the Stream items, it's the same to the
    ``Stream.take()`` method.

    See Also
    --------
    Stream.take :
      Returns the n first elements from the Stream, removing them.

    Note
    ----
    When applied in a StreamTeeHub, this method doesn't consume a copy.
    Data evaluation is done only once, i.e., after peeking the data is simply
    stored to be yielded again when asked for.

    """
    return self.copy().take(n=n, constructor=constructor)

  def skip(self, n):
    """
    Throws away the first ``n`` values from the Stream.

    Note
    ----
    Performs the evaluation lazily, i.e., the values are thrown away only
    after requesting the next value.

    """
    def skipper(data):
      for _ in xrange(int(round(n))):
        next(data)
      for el in data:
        yield el

    self._data = skipper(self._data)
    return self

  def limit(self, n):
    """
    Enforces the Stream to finish after ``n`` items.
    """
    data = self._data
    self._data = (next(data) for _ in xrange(int(round(n))))
    return self

  def __getattr__(self, name):
    """
    Returns a Stream of attributes or methods, got in an elementwise fashion.
    """
    if name == NEXT_NAME:
      raise AttributeError("Streams are iterable, not iterators")
    return Stream(getattr(a, name) for a in self._data)

  def __call__(self, *args, **kwargs):
    """
    Returns the results from calling elementwise (where each element is
    assumed to be callable), with the same arguments.
    """
    return Stream(a(*args, **kwargs) for a in self._data)

  def append(self, *other):
    """
    Append self with other stream(s). Chaining this way has the behaviour:

      ``self = Stream(self, *others)``

    """
    self._data = it.chain(self._data, Stream(*other)._data)
    return self

  def map(self, func):
    """
    A lazy way to apply the given function to each element in the stream.
    Useful for type casting, like:

    >>> from audiolazy import count
    >>> count().take(5)
    [0, 1, 2, 3, 4]
    >>> my_stream = count().map(float)
    >>> my_stream.take(5) # A float counter
    [0.0, 1.0, 2.0, 3.0, 4.0]

    """
    self._data = xmap(func, self._data)
    return self

  def filter(self, func):
    """
    A lazy way to skip elements in the stream that gives False for the given
    function.
    """
    self._data = xfilter(func, self._data)
    return self

  @classmethod
  def register_ignored_class(cls, ignore):
    cls.__ignored_classes__ += (ignore,)

  def __abs__(self):
    return self.map(abs)


Question: import pytest
import itertools as it
import os, json
from ..lazy_auditory import erb, gammatone_erb_constants, gammatone, phon2dB
from ..lazy_misc import almost_eq, sHz
from ..lazy_math import pi
from ..lazy_filters import CascadeFilter
from ..lazy_stream import Stream
from ..lazy_compat import iteritems
# -*- coding: utf-8 -*-
# This file is part of AudioLazy, the signal processing Python package.
# Copyright (C) 2012-2016 Danilo de Jesus da Silva Bellini
#
# AudioLazy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Testing module for the lazy_auditory module
"""

from __future__ import division

p = pytest.mark.parametrize


# Audiolazy internal imports


class TestERB(object):

  @p(("freq", "bandwidth"),
     [(1000, 132.639),
      (3000, 348.517),
     ])
  def test_glasberg_moore_slaney_example(self, freq, bandwidth):

Answer:     assert almost_eq.diff(erb["gm90"](freq), bandwidth, max_diff=5e-4)