audiolazy
Expressive Digital Signal Processing (DSP) package for Python.
AudioLazy
=========
Lazyness 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 lazyness 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;
- Strongly sample-based representation (Stream class) with easy conversion
to block representation using the ``Stream.blocks(size, hop)`` method;
- Sample-based interactive processing with ``ControlStream``;
- ``Streamix`` mixer for iterables given their starting time deltas;
- Multi-thread audio I/O integration with PyAudio;
- 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;
- Zeros and poles plots and frequency response plotting integration with
MatPlotLib;
- Linear Predictive Coding (LPC) directly to ``ZFilter`` instances, from
which you can find PARCOR coeffs and LSFs;
- Both sample-based (e.g., zero-cross rate, envelope, moving average,
clipping, unwrapping) and block-based (e.g., window functions, DFT,
autocorrelation, lag matrix) analysis and processing tools;
- A simple synthesizer (Table lookup, Karplus-Strong) with processing tools
(Linear ADSR envelope, fade in/out, fixed duration line stream) and basic
wave data generation (sinusoid, white noise, impulse);
- Biological auditory periphery modeling (ERB and gammatone filter models);
- 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;
- Polynomials, Stream-based functions from itertools, math, cmath, and more!
Go try yourself! =)
Installing
----------
The package works both on Linux and on Windows. You can find the last stable
version at `PyPI <http://pypi.python.org/pypi/audiolazy>`_ and install it with
the usual Python installing mechanism::
python setup.py install
If you have pip, you can go directly (use ``-U`` for update or reinstall)::
pip install audiolazy
for downloading (from PyPI) and installing the package for you, or::
pip install -U .
To install from a path that has the ``setup.py`` file and the package data
uncompressed previously.
For the *bleeding-edge* version, you can install directly from the github
repository (requires ``git`` for cloning)::
pip install -U git+git://github.com/danilobellini/audiolazy.git
For older versions, you can install from the PyPI link or directly from the
github repository, based on the repository tags. For example, to install the
version 0.03 (requires ``git`` for cloning)::
pip install -U git+git://github.com/danilobellini/audiolazy.git@v0.03
The package doesn't have any strong dependency for its core besides the Python
itself and its standard library, but you might need:
- PyAudio: needed for playing and recording audio (``AudioIO`` class);
- NumPy: needed for doing some maths, such as finding the LSFs from a filter
or roots from a polynomial;
- MatPlotLib: needed for all default plotting, like in ``LinearFilter.plot``
method and several examples;
- SciPy (testing only): used as an oracle for LTI filter testing;
- pytest and pytest-cov (testing only): runs test suite and shows code
coverage status;
- wxPython (example only): used by one example with FM synthesis in an
interactive GUI;
- Music21 (example only): there's one example that gets the Bach chorals from
that package corpora for synthesizing and playing;
- Sphinx (documentation only): it can create the software documentation in
several different file formats.
Beside examples and tests, only the filter plotting with ``plot`` and
``zplot`` methods needs MatPlotLib. Also, the routines that needs NumPy up to
now are:
- Root finding with ``zeros`` and ``poles`` properties (filter classes) or
with ``roots`` property (Poly class);
- Some Linear Predictive Coding (``lpc``) strategies: ``nautocor``,
``autocor`` and ``covar``;
- Line Spectral Frequencies ``lsf`` and ``lsf_stable`` functions.
Getting started
---------------
Before all examples below, it's easier to get everything from audiolazy
namespace:
.. code-block:: python
from audiolazy import *
All modules starts with "lazy\_", but their data is already loaded in the main
namespace. These two lines of code do the same thing:
.. code-block:: python
from audiolazy.lazy_stream import Stream
from audiolazy import Stream
Endless iterables with operators (be careful with loops through an endless
iterator!):
.. code-block:: python
>>> a = Stream(2) # Periodic
>>> b = Stream(3, 7, 5, 4) # Periodic
>>> c = a + b # Elementwise sum, periodic
>>> c.take(15) # First 15 elements from the Stream object
[5, 9, 7, 6, 5, 9, 7, 6, 5, 9, 7, 6, 5, 9, 7]
And also finite iterators (you can think on any Stream as a generator with
elementwise operators):
.. code-block:: python
>>> a = Stream([1, 2, 3, 2, 1]) # Finite, since it's a cast from an iterable
>>> b = Stream(3, 7, 5, 4) # Periodic
>>> c = a + b # Elementwise sum, finite
>>> list(c)
[4, 9, 8, 6, 4]
LTI Filtering from system equations (Z-transform). After this, try summing,
composing, multiplying ZFilter objects:
.. code-block:: python
>>> filt = 1 - z ** -1 # Diff between a sample and the previous one
>>> filt
1 - 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.Stream object at ...>
>>> data *= 10 # Elementwise gain
>>> [int(round(x)) for x in data] # 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):
.. code-block:: python
(1 + z ** -2).plot().show()
.. image:: https://raw.github.com/danilobellini/audiolazy/master/images/filt_plot.png
But 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):
.. code-block:: python
(1 + z ** -2).plot().savefig("my_plot.pdf")
On the other hand, you can always show the figure using MatPlotLib directly:
.. code-block:: python
from matplotlib import pyplot as plt # Or "import pylab as plt"
filt = 1 + z ** -2
fig1 = filt.plot(plt.figure()) # Argument not needed on the first figure
fig2 = filt.zplot(plt.figure()) # The argument ensures a new figure
plt.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):
.. code-block:: python
filt1 = CascadeFilter(0.2 - z ** -3) # 3 zeros
filt2 = CascadeFilter(1 / (1 -.8 * z ** -1 + .6 * z ** -2)) # 2 poles
# Here __add__ concatenates and __mul__ by an integer make reference copies
filt = (filt1 * 5 + filt2 * 10) # 15 zeros and 20 poles
filt.zplot().show()
.. image:: https://raw.github.com/danilobellini/audiolazy/master/images/cascade_plot.png
Linear Predictive Coding (LPC) autocorrelation method analysis filter
frequency response plot (needs MatPlotLib):
.. code-block:: python
lpc([1, -2, 3, -4, -3, 2, -3, 2, 1], order=3).plot().show()
.. image:: https://raw.github.com/danilobellini/audiolazy/master/images/lpc_plot.png
Linear Predictive Coding covariance method analysis and synthesis filter,
followed by the frequency response plot together with block data DFT
(MatPlotLib):
.. code-block:: python
>>> data = Stream(-1., 0., 1., 0.) # Periodic
>>> blk = data.take(200)
>>> analysis_filt = lpc.covar(blk, 4)
>>> analysis_filt
1 + 0.5 * z^-2 - 0.5 * z^-4
>>> residual = list(analysis_filt(blk))
>>> residual[:10]
[-1.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
>>> synth_filt = 1 / analysis_filt
>>> synth_filt(residual).take(10)
[-1.0, 0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, -1.0, 0.0]
>>> amplified_blk = list(Stream(blk) * -200) # For alignment w/ DFT
>>> synth_filt.plot(blk=amplified_blk).show()
.. image:: https://raw.github.com/danilobellini/audiolazy/master/images/dft_lpc_plot.png
AudioLazy doesn't need any audio card to process audio, but needs PyAudio to
play some sound:
.. code-block:: python
rate = 44100 # Sampling rate, in samples/second
s, Hz = sHz(rate) # Seconds and hertz
ms = 1e-3 * s
note1 = karplus_strong(440 * Hz) # Pluck "digitar" synth
note2 = zeros(300 * ms).append(karplus_strong(880 * Hz))
notes = (note1 + note2) * .5
sound = notes.take(int(2 * s)) # 2 seconds of a Karplus-Strong note
with AudioIO(True) as player: # True means "wait for all sounds to stop"
player.play(sound, rate=rate)
See also the docstrings and the "examples" directory at the github repository
for more help. Also, the huge test suite might help you understanding how the
package works and how to use it.
..
This file is part of AudioLazy, the signal processing Python package.
Copyright (C) 2012 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/>.
danilo [dot] bellini [at] gmail [dot] com
AudioLazy changes history
-------------------------
*** Version 0.04 (Documentation, LPC, Plots!) ***
+ examples:
- Random Bach Choral playing example (needs Music21 corpus)
+ general:
- Sphinx documentation!
- Self-generated package and module summary at the docstring
- Integration with NumPy (tested on 1.5.0, 1.6.1 and 1.6.2) and MatPlotLib
(tested on 1.0.1 and 1.2.0)
- More docstrings and doctests, besides lots of corrections
- Itemized package description, installation instructions and getting
started examples with plots in README.rst
- Now with 5400+ tests and 75% code coverage
+ lazy_analysis:
- One-dimensional autocorrelation function with ``acorr`` and lag
"covariance" (due to lpc.covar) with ``lag_matrix``
- DFT for any frequency, given a block
- Three envelope filtering strategies (time domain)
- Three moving average filter strategies
- Signal clipping function
- Signal unwrap, defaults to the ``2 * pi`` radians range but configurable
to other units and max signal difference allowed
- New AMDF algorithm as a non-linear filter
+ lazy_core:
- StrategyDict instances now are singletons of a new class, which have
lazy non-memoized docstrings based on their contents
+ lazy_filters:
- ZFilter composition/substitution, e.g., ``(1 + z ** -1)(1 / z)`` results
to the ZFilter instance ``1 + z``
- 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
- More LinearFilter behaviour into FilterList: Plotting (``plot`` and
``zplot``), ``poles``, ``zeros``, ``is_lti`` and ``is_causal``
- New ``ParallelFilter`` class, inheriting from FilterList
- Now comb is a StrategyDict too, with 3 strategies:
* ``comb.fb`` (default): Feedback comb filter (IIR or time variant)
* ``comb.tau``: Same to the feedback strategy, but with a time decay
``tau`` parameter (time in samples up to ``1/e`` amplitude, or
-8.686 dB) instead of a gain ``alpha``
* ``comb.ff``: Feed-forward comb filter (FIR or time variant)
+ lazy_lpc (*new!*):
- Linear Predictive Coding (LPC) coefficients as a ZFilter from:
* ``lpc.autocor`` (default): Auto-selects autocorrelation implementation
(Faster)
* ``lpc.nautocor``: Autocorrelation, with linear system solved by NumPy
(Safer)
* ``lpc.kautocor``: Autocorrelation, using the Levinson-Durbin algorithm
* ``lpc.covar`` or ``lpc.ncovar``: Covariance, with linear system solved
by NumPy
* ``lpc.kcovar``: Covariance, slower. Mainly for those without NumPy
* ``levinson_durbin``: Same to the ``lpc.kautocor``, but with the
autocorrelation vector as the input, not the signal data
- Toeplitz matrix as a list of lists
- Partial correlation coefficients (PARCOR) or reflection coefficients
- Line Spectral Frequencies (LSF)
- Stability testers for filters with LSF and PARCOR
+ lazy_math:
- New ``sign`` gets the sign of a given sequence.
+ lazy_midi:
- Completed converters between frequency (in hertz), string and MIDI pitch
numbers
- New ``octaves`` for finding all octaves in a frequency range given one
frequency
+ lazy_misc:
- New ``rational_formatter``: casts floats to strings, perhaps with a symbol
string as multiplier
- New ``pi_formatter``: same to ``rational_formatter``, but with the symbol
fixed to pi, mainly for use in MatPlotLib labels
+ lazy_poly:
- New Poly.roots property, based on NumPy
+ lazy_stream:
- Streamix class for mixing Streams based on delta starting times,
automatically managing the need for multiple "tracks"
+ lazy_synth:
- Karplus-Strong algorithm now uses ``tau`` time decay constant instead of
the comb filter ``alpha`` gain.
*** Version 0.03 (Time variant filters, examples, etc.. Major changes!) ***
+ examples (*new!*):
- Gammatone frequency and impulse response plots example
- FM synthesis example for benchmarking with CPython and PyPy
- Simple I/O wire example, connecting the input directly to the output
- Modulo Counter graphics w/ FM synthesis audio in a wxPython application
- Window functions plot example (all window strategies)
+ general:
- Namespace cleanup with __all__
- Lots of optimization and refactoring, also on tests and setup.py
- Better docstrings and README.rst
- Doctests (with pytest) and code coverage (needs pytest-cov)
- Now with 5200+ tests and 79% code coverage
+ lazy_analysis (*new!*):
- New ``window`` StrategyDict instance, with:
* Hamming (default)
* Hann
* Rectangular
* Bartlett (triangular with zero endpoints)
* Triangular (without zeros)
* Blackman
+ lazy_auditory (*new!*):
- Two ERB (Equivalent Rectangular Bandwidth) models (both by Glasberg and
Moore)
- Function to find gammatone bandwidth from ERB for any gammatone order
- Three gammatone filter implementations: sampled impulse response, Slaney,
Klapuri
+ lazy_core:
- MultiKeyDict: an "inversible" dict (i.e., a dict whose values must be
hasheable) that may have several keys for each value
- StrategyDict: callable dict to store multiple function implementations
in. Inherits from MultiKeyDict, so the same strategy may have multiple
names. It's also an iterable on its values (functions)
+ lazy_filters:
- LTI and LTIFreq no longer exists! They were renamed to LinearFilter and
ZFilter since filters now can have Streams as coefficients (they don't
need to be "Time Invariant" anymore)
- Linear filters are now iterables, allowing:
* Comparison with almost_eq like ``assert almost_eq(filt1, filt2)``
* Expression like ``numerator_data, denominator_data = filt``, where
each data is a list of pairs that can be used as input for Poly,
LinearFilter or ZFilter
- LinearFilterProperties class, implementing numlist, denlist, numdict and
dendict, besides numerator and denominator, from numpoly and denpoly
- Comparison "==" and "!=" are now strict
- CascadeFilter: list of filters that behave as a filter
- LinearFilter.__call__ now has the "zero" optional argument (allows
non-float)
- LinearFilter.__call__ memory input can be a function or a Stream
- LinearFilter.linearize: linear interpolated delay-line from fractional
delays
- Feedback comb filter
- 4 resonator filter models with 2-poles with exponential approximation
for finding the radius from the bandwidth
- Simple one pole lowpass and highpass filters
+ lazy_io:
- AudioIO.record method, creating audio Stream instances from device data
+ lazy_itertools:
- Now with a changed tee function that allows not-iterable inputs,
helpful to let the same code work with Stream instances and constants
+ lazy_math (*new!*):
- dB10, dB20 functions for converting amplitude (squared or linear,
respectively) to logarithmic dB (power) values from complex-numbers
(like the ones returned by LinearFilter.freq_response)
- Most functions from math module, but working decorated with elementwise
(``sin``, ``cos``, ``sqrt``, etc.), and the constants ``e`` and ``pi``
- Other functions: ``factorial``, ``ln`` (the ``log`` from math), ``log2``,
``cexp`` (the ``exp`` from cmath) and ``phase`` (from cmath)
+ lazy_midi:
- MIDI pitch numbers and Hz frequency converters from strings like "C#4"
+ lazy_misc:
- Elementwise decorator now based on both argument keyword and position
+ lazy_poly:
- Horner-like scheme for Poly.__call__ evaluation
- Poly now can have Streams as coefficients
- Comparison "==" and "!=" are now strict
+ lazy_stream:
- Methods and attributes from Stream elements can be used directly,
elementwise, like ``my_stream.imag`` and ``my_stream.conjugate()`` in a
stream with complex numbers
- New thub() function and StreamTeeHub class: tee (or "T") hub auto-copier
to help working with Stream instances *almost* the same way as you do with
numbers
+ lazy_synth:
- Karplus-Strong synthesis algorithm
- ADSR envelope
- Impulse, ones, zeros/zeroes and white noise Stream generator
- Faster sinusoid not based on the TableLookup class
*** Version 0.02 (Interactive Stream objects & Table lookup synthesis!) ***
+ general:
- 10 new tests
+ lazy_midi (*new!*):
- MIDI to frequency (Hz) conversor
+ lazy_misc:
- sHz function for explicit time (s) and frequency (Hz) units conversion
+ lazy_stream:
- Interactive processing with ControlStream instances
- Stream class now allows inheritance
+ lazy_synth (*new!*):
- TableLookup class, with sinusoid and sawtooth instances
- Endless counter with modulo, allowing Stream inputs, mainly created for
TableLookup instances
- Line, fade in, fade out, ADS attack with endless sustain
*** Version 0.01 (First "pre-alpha" version!) ***
+ general:
- 4786 tests (including parametrized tests), based on pytest
+ lazy_core:
- AbstractOperatorOverloaderMeta class to help massive operator
overloading as needed by Stream, Poly and LTIFreq (now ZFilter) classes
+ lazy_filters:
- LTI filters, callable objects with operators and derivatives, returning
Stream instances
- Explicit filter formulas with the ``z`` object, e.g.
``filt = 1 / (.5 + z ** -1)``
+ lazy_io:
- Multi-thread audio playing (based on PyAudio), with context manager
interface
+ lazy_itertools:
- Stream-based version of all itertools
+ lazy_misc:
- Block-based processing, given size and (optionally) hop
- Simple zero padding generator
- Elementwise decorator for functions
- Bit-based and diff-based "almost equal" comparison function for floats
and iterables with floats. Also works with (finite) generators
+ lazy_poly:
- Poly: polynomials based on dictionaries, with list interface and
operators
+ lazy_stream:
- Stream: each instance is basically a generator with elementwise
operators
- Decorator ``tostream`` so generator functions can return Stream objects
Danilo de Jesus da Silva Bellini
47164a043e427dd8b593eb9e91d4c2d3a013ac08
0.04