AudioLazy

Real-Time Expressive Digital Signal Processing (DSP) Package for Python!

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=30s, tau=40s) 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! =)

GitHub