audiolazy 0.05

Laziness and object representation

There are several tools and packages that let the Python use and
expressiveness look like languages such as MatLab and Octave. However, the
eager evaluation done by most of these tools make it difficult, perhaps
impossible, to use them for real time audio processing. To avoid such
eagerness, one can make the calculations only when data is requested, not
when the path to the data is given. This is the core idea in laziness that
allows:

Real-time application (you don’t need to wait until all data is
processed to have a result);

Endless data sequence representation;

Data-flow representation;

Task elimination when a reverse task is done: instead of doing something
to then undo, nothing needs to be done, and no conscious optimization
need to be done for that.

Another difficulty concerns expressive code creation for audio processing in
blocks through indexes and vectors. Sometimes, that’s unavoidable, or at
least such avoidance would limit the power of the system that works with
sequence data.

Block sequences can be found from sample sequences being both objects, where
the latter can be the result of a method or function over the former. The
information needed for such is the block size and where would start the next
block. Although one can think about the last block and the exact index where
it would start, most of the time spent in steps like this one happens to be
an implementation issue that just keep the focus away from the problem being
worked on. To allow a thing like an endless data sequence, there should be
no need to know when something stops.

Probably an engineer would find the use of equations and structures from
electrical engineering theory much cleaner to understand than storing
everything into data arrays, mainly when common operations are done to these
representations. What is the product of the filter with numerator
[1, 7, 2] and denominator [1, 0.5, 0.2] as its system equation with
the one that has the arrays reversed like [2, 7, 1]? That might be simple,
and the reversed would avoid questions like “what comes first, the zero or the
[minus] two exponent?”, but maybe we could get more efficient ourselves if we
had something easier: multiplication could be written once and for all and
with a representation programmers are used to see. This would be even more
expressive if we could get rid from the asymmetry of a method call like
filt1.multiply_by(filt2), since multiplication in this case should be
commutative. The use of natural operators is possible in a language that
allows operator overloading, but for such we need to describe
those equations and structures as objects and object relationships.

The name Hz can be a number that would allow conversion to a default DSP
internal rad/samples unit, so one can write things like freq = 440 * Hz.
This isn’t difficult in probably any language, but can help in expressiveness,
already. If (almost) everything would need data in “samples” or “rad/sample”
units, constants for converting these from “second” and “hertz” would help
with the code expressiveness. A comb filter comb.tau(delay=30*s, tau=40*s)
can represent a comb filter with the given delay and time constant, both in
samples, but with a more clear meaning for the reader than it would have with
an expression like [1] + [0] * 239999 + [alpha]. Would it be needed to
store all those zeros while just using the filter to get a frequency response
plot?

It’s possible to avoid some of these problems with well-chosen constants,
duck typing, overloaded operators, functions as first-class citizens, object
oriented together with functional style programming, etc.., resources
that the Python language gives us for free.

What does it do?

Prioritizing code expressiveness, clarity and simplicity, without precluding
the lazy evaluation, and aiming to be used together with Numpy, Scipy and
Matplotlib as well as default Python structures like lists and generators,
AudioLazy is a package written in pure Python proposing digital audio signal
processing (DSP), featuring:

A Stream class for finite and endless signals representation with
elementwise operators (auto-broadcast with non-iterables) in a common
Python iterable container accepting heterogeneous data;

Linear filtering with Z-transform filters directly as equations (e.g.
filt = 1 / (1 - .3 * z ** -1)), including linear time variant filters
(i.e., the a in a * z ** k can be a Stream instance), cascade
filters (behaves as a list of filters), resonators, etc.. Each
LinearFilter instance is compiled just in time when called;

Multiple implementation organization as StrategyDict instances:
callable dictionaries that allows the same name to have several different
implementations (e.g. erb, gammatone, lowpass, resonator,
lpc, window);

Converters among MIDI pitch numbers, strings like “F#4” and frequencies;

>>>filt=1-z**-1# Diff between a sample and the previous one>>>filt1-z^-1>>>data=filt([.1,.2,.4,.3,.2,-.1,-.3,-.2])# Past memory has 0.0>>>data# This should have internally [.1, .1, .2, -.1, -.1, -.3, -.2, .1]<audiolazy.lazy_stream.Streamobjectat...>>>>data*=10# Elementwise gain>>>[int(round(x))forxindata]# Streams are iterables[1,1,2,-1,-1,-3,-2,1]>>>data_int=filt([1,2,4,3,2,-1,-3,-2],zero=0)# Now zero is int>>>list(data_int)[1,1,2,-1,-1,-3,-2,1]

LTI Filter frequency response plot (needs MatPlotLib):

(1+z**-2).plot().show()

The matplotlib.figure.Figure.show method won’t work unless you’re
using a newer version of MatPlotLib (works on MatPlotLib 1.2.0), but you still
can save the above plot directly to a PDF, PNG, etc. with older versions
(e.g. MatPlotLib 1.0.1):

(1+z**-2).plot().savefig("my_plot.pdf")

On the other hand, you can always show the figure using MatPlotLib directly:

frommatplotlibimportpyplotasplt# Or "import pylab as plt"filt=1+z**-2fig1=filt.plot(plt.figure())# Argument not needed on the first figurefig2=filt.zplot(plt.figure())# The argument ensures a new figureplt.show()

CascadeFilter instances and ParallelFilter instances are lists of filters with
the same operator behavior as a list, and also works for plotting linear
filters. Constructors accepts both a filter and an iterable with filters.
For example, a zeros and poles plot (needs MatPlotLib):

Common place for iterable-based version of itertools/built-ins in both
Python 2 and 3 starting with “x”: xmap, xfilter, xzip,
xrange, xzip_longest. Versions with “i” are kept in lazy_itertools
module to return Stream instances (imap, izip, izip.longest,
etc.), and Python 2 list-based behaviour of range is kept as
orange (a fruitful name)

New meta function for creating metaclasses always in a “Python 3
look-alike” style, keeping the semantics (including the inheritance
hierarchy, which won’t have any extra “dummy” class)

lazy_core:

New OpMethod class with 33 operator method instances and querying

Changed AbstractOperatorOverloaderMeta to the new OpMethod-based
interface

Now StrategyDict changes the module __test__ so that doctests from
strategies are found by the doctest finder.

lazy_filters:

ZFilter instances are now better prepared for Stream coeffs and
operator-based filter creation, as well as a new copy helper method

Filters are now hashable (e.g., they can be used in sets)

lazy_io:

New RecStream class for recording Stream instances with a stop method

Now chunks is a StrategyDict here, instead of two lazy_misc functions

Now the default chunk size is stored in chunks.size, and can be changed

lazy_itertools:

New accumulate itertool from Python 3, available also in Python 2
yielding a Stream. This is a new StrategyDict with one more strategy in
Python 3

Strategy chain.from_iterable is now available (Stream version
itertool), and chain is now a StrategyDict

Now izip is a StrategyDict, with izip.smallest (default) and
izip.longest strategies

lazy_misc:

New rint for “round integer” operations as well as other higher step
integer quantization

Now almost_eq is a single StrategyDict with both bits (default,
comparison by significand/mantissa bits) and diff (absolute value
difference) strategies

lazy_poly:

New x Poly object (to be used like the z ZFilter instance)

Waring-Lagrange polynomial interpolator StrategyDict

General resample based on Waring-Lagrange interpolators, working with
time-varying sample rate

New methods Poly.is_polynomial() and Poly.is_laurent()

New property Poly.order for common polynomials

Now Poly.integrate() and Poly.diff() methods returns Poly
instances, and the zero from the caller Poly is always kept in
result (this includes many bugfixes)

Poly instances are now better prepared for Stream coeffs and evaluation,
including a helper Poly.copy() method

Poly is now hashable and have __setitem__ (using both isn’t allowed for
the same instance)

lazy_stream:

Stream.take now accepts floats, so with first sHz output as
s (for second) you can now use my_stream.take(20 * s) directly,
as well as a “take all” feature my_stream.take(inf)

New Stream.peek() method, allowing taking items while keeping them
as the next to be yielded by the Stream or StreamTeeHub

New Stream.skip() method for neglecting the leading items without
storing them

New Stream.limit() method, to enforce a maximum “length”

StreamTeeHub methods skip(), limit(), append(), map() and
filter() returns the modified copy as a Stream instance (i.e., works
like Stream(my_stream_tee_hub).method_name())

Control over the module name in tostream (needed for lazy_itertools)

lazy_synth:

Input “dur” in ones(), zeros(), white_noise() and
impulse() now can be inf (besides None)

New LinearFilter.plot() directly plots the frequency response of a LTI
filter to a MatPlotLib figure. Configurable:

Linear (default) or logarithmic frequency scale

Linear, squared or dB (default) magnitude scale

Plots together the DFT of a given block, if needed. Useful for LPC

Phase unwrapping (defaults to True)

Allows frequency in Hz and in rad/sample. When using radians units,
the tick locator is based on pi, as well as the formatter

New LinearFilter.zplot() for plotting the zero-pole plane of a LTI filter
directly into a MatPlotLib figure

New LinearFilterProperties read-only properties numpolyz and
denpolyz returning polynomials based on x = z instead of the
polynomials based on x = z ** -1 returned from numpoly and
denpoly

New LinearFilter properties poles and zeros, based on NumPy

New class FilterList for filter grouping with a callables
property, for casting from lists with constant gain values as filters.
It is an instance of FilterListMeta (old CascadeFilterMeta), and
CascadeFilter now inherits from this FilterList