Slides da apresentação no DevInSampa 2013 (18 de maio), com algumas complementações e correções. Esses mesmos slides foram utilizados na apresentação feita no fisl 2014 (2013-07-05).
(2013-07-05) [fisl] Semáforo Gráfico dose para TDD em dojos
AudioLazy DSP Python
1. AudioLazy - DSP expressivo e em
tempo real para o Python
http://pypi.python.org/pypi/audiolazyhttp://pypi.python.org/pypi/audiolazy
Copyright (C) 2012-2013Copyright (C) 2012-2013
Danilo de Jesus da Silva Bellini <danilo.bellini@gmail.com>Danilo de Jesus da Silva Bellini <danilo.bellini@gmail.com>
6. Container para áudio
● Tempo real
– Amostras (dados/elementos) inexistentes...
● ...em tempo de compilação (dados a serem coletados)
● ...em tempo de execução (dados criados no futuro)
– Duração possivelmente indefinida
– Não deve ser necessário computar tudo para começar
a apresentar o resultado
● Resultados parciais
● Para cada amostra de entrada, deve haver uma de saída
– Minimizar lag (atraso) entre entrada e saída
Avaliação tardia!
7. Classe Stream
● Iterável
● Heterogêneo
● Operadores
– Elementwise/broadcast
como no NumPy
● Avaliação tardia
– Lembra geradores
● Já estávamos usando!
– sinusoid,
karplus_strong,
ControlStream, ...
● Ausência de índices
– Limite de representação
inteira (32 bits
estouraria em 27:03:12)
In [1]: from audiolazy import Stream
In [2]: dados = Stream(5, 7, 1, 2, 5, 3, 2) # Periódico
In [3]: dados2 = Stream(0, 1) # Idem
In [4]: (dados + dados2).take(15)
Out[4]: [5, 8, 1, 3, 5, 4, 2, 6, 7, 2, 2, 6, 3, 3, 5]
In [1]: from audiolazy import Stream
In [2]: dados = Stream(5, 7, 1, 2, 5, 3, 2) # Periódico
In [3]: dados2 = Stream(0, 1) # Idem
In [4]: (dados + dados2).take(15)
Out[4]: [5, 8, 1, 3, 5, 4, 2, 6, 7, 2, 2, 6, 3, 3, 5]
8. Classe Stream
● Métodos, atributos e propriedades são aplicados
elemento a elemento
– Exceto quando existe na classe Stream (“take”,
“blocks”, “peek”, “skip”, “limit”, ...)
● Finito ou de finalização indeterminada
In [5]: Stream([2, 3, 4]).take(5) # Lista de entrada
Out[5]: [2, 3, 4]
In [6]: Stream(2, 3, 4).take(5) # Números de entrada
Out[6]: [2, 3, 4, 2, 3]
In [7]: Stream(*[2, 3, 4]).take(5) # Lista com "*"
Out[7]: [2, 3, 4, 2, 3]
In [8]: (2 * Stream([1 + 2j, -3j, 7]).real).take(inf)
Out[8]: [2.0, 0.0, 14]
In [5]: Stream([2, 3, 4]).take(5) # Lista de entrada
Out[5]: [2, 3, 4]
In [6]: Stream(2, 3, 4).take(5) # Números de entrada
Out[6]: [2, 3, 4, 2, 3]
In [7]: Stream(*[2, 3, 4]).take(5) # Lista com "*"
Out[7]: [2, 3, 4, 2, 3]
In [8]: (2 * Stream([1 + 2j, -3j, 7]).real).take(inf)
Out[8]: [2.0, 0.0, 14]
9. Decorador tostream:
Geradores convertidos em Stream
● Já foi aplicado a TODOS os itertools (e.g. count)!!!
In [1]: from audiolazy import tostream
In [2]: @tostream
...: def impulse():
...: yield 1
...: while True:
...: yield 0
...:
In [3]: impulse # De fato, uma função
Out[3]: <function __main__.impulse>
In [4]: impulse() # Devolve um objeto Stream
Out[4]: <audiolazy.lazy_stream.Stream at 0x30824d0>
In [5]: impulse().take(5)
Out[5]: [1, 0, 0, 0, 0]
In [6]: (impulse() + 1).take(5) # Outro objeto instanciado
Out[6]: [2, 1, 1, 1, 1]
In [1]: from audiolazy import tostream
In [2]: @tostream
...: def impulse():
...: yield 1
...: while True:
...: yield 0
...:
In [3]: impulse # De fato, uma função
Out[3]: <function __main__.impulse>
In [4]: impulse() # Devolve um objeto Stream
Out[4]: <audiolazy.lazy_stream.Stream at 0x30824d0>
In [5]: impulse().take(5)
Out[5]: [1, 0, 0, 0, 0]
In [6]: (impulse() + 1).take(5) # Outro objeto instanciado
Out[6]: [2, 1, 1, 1, 1]
Síntese
personalizada!
(Acesso direto
às amostras)
10. Processamento em bloco
● Stream.blocks(size, hop)
– Qualquer salto (hop) positivo
– Se mudar a saída, a mudança persistirá na próxima
saída quando hop < size
● Saídas são a mesma fila circular implementada como
collections.deque
In [1]: data = Stream([1, 2, 3, 4, 5])
In [2]: blks = data.blocks(size=2, hop=1)
In [3]: [list(blk) for blk in blks]
Out[3]: [[1, 2], [2, 3], [3, 4], [4, 5]]
In [1]: data = Stream([1, 2, 3, 4, 5])
In [2]: blks = data.blocks(size=2, hop=1)
In [3]: [list(blk) for blk in blks]
Out[3]: [[1, 2], [2, 3], [3, 4], [4, 5]]
12. Filtros LTI
(Lineares e invariantes no tempo)
““Digital signal processing is mainlyDigital signal processing is mainly
based on linear time-invariantbased on linear time-invariant
systems.systems.””
(Dutilleux, Dempwolf, Holters e Zölzer(Dutilleux, Dempwolf, Holters e Zölzer
DAFx, segunda edição, capítulo 4, p. 103)DAFx, segunda edição, capítulo 4, p. 103)
13. Transformada Z
● Definição:
● Interpretação:
–Atrasoem k
amostras!
● Muitos “infinitos”
– Teoremas
● Possibilitam o uso prático
(eliminam os “infinitos”)
14. Exemplo de transformada Z
● Acumulador ● Média móvel
Tá legal...mas, na prática, cadê o tal código?!
Saída / Entrada, ou
H(z) = Y(z) / X(z)
15. Objeto “z”
In [1]: from audiolazy import z, Stream, maverage
In [2]: M = 5
In [3]: media_movel_5 = (1 - z ** -M) / (M * (1 - z ** -1))
In [4]: acumulador = 1 / (1 - z ** -1)
In [5]: media_movel_5(Stream(5)).take(10)
Out[5]: [1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0]
In [6]: acumulador(Stream(5)).take(10)
Out[6]: [5.0, 10.0, 15.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0, 50.0]
In [7]: maverage.recursive(4)
Out[7]:
0.25 - 0.25 * z^-4
------------------
1 - z^-1
In [1]: from audiolazy import z, Stream, maverage
In [2]: M = 5
In [3]: media_movel_5 = (1 - z ** -M) / (M * (1 - z ** -1))
In [4]: acumulador = 1 / (1 - z ** -1)
In [5]: media_movel_5(Stream(5)).take(10)
Out[5]: [1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0]
In [6]: acumulador(Stream(5)).take(10)
Out[6]: [5.0, 10.0, 15.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0, 50.0]
In [7]: maverage.recursive(4)
Out[7]:
0.25 - 0.25 * z^-4
------------------
1 - z^-1
Filtros LTI, em geral:
16. Filtros prontos!
● Média móvel
● Ressonadores
● Comb
● Passa-baixas e passa-altas
● Gammatone (Patterson-Holdsworth, audição)
– Slaney
– Klapuri
● 4 ressonadores em cascata
– Implementação genérica (qualquer ordem)
● Teoremas (parte de meu mestrado)
Filtros variantes no tempo!
Coeficientes “a” em de a * z ** -k
podem ser objetos Stream)
17. Plot (AudioLazy + MatPlotLib)!
● DTFT - Caso particular da transformada Z
– O valor de z está na circunferência complexa unitária
● Método plot dos filtros
– Resposta em frequência
● Método zplot
– Estabilidade do filtro
– Pólos: “X”
● Raízes do denominador
– Zeros: “O”
● Raízes do numerador
X
X
MatPlotLib
faz melhor
que isto...
18. Considerações finais sobre filtros
● Implementação direta I
– Evita multiplicação por 1
– Não cria os termos com coeficiente nulo
● JIT (Just in Time)
– Cada filtro é criado e compilado em tempo de
execução
– Permite filtros variantes no tempo gerais e (até certo
ponto) eficientes
19. Parte 4 - Cálculo numérico (e um
pouco de simbólico também)
Heeeeeey, não era sobre áudio?Heeeeeey, não era sobre áudio?
20. Exemplos
● Pi (exemplo no repositório da AudioLazy)
– Série de Madhava-Gregory-Leibniz
● Fibonacci
atan(v)=v−
v3
3
+
v5
5
−
v7
7
+
v9
9
...
atan(1)= π
4
In [2]: (z ** -1 / (1 - z ** -1 - z ** -2))(impulse()).take(10)
Out[2]: [0.0, 1.0, 1.0, 2.0, 3.0, 5.0, 8.0, 13.0, 21.0, 34.0]
In [2]: (z ** -1 / (1 - z ** -1 - z ** -2))(impulse()).take(10)
Out[2]: [0.0, 1.0, 1.0, 2.0, 3.0, 5.0, 8.0, 13.0, 21.0, 34.0]
h[n]=h[n−1]+h[ n−2]+δ[ n−1]
21. Polinômios
● Necessário para os filtros lineares
● Baseados em dicionário
– Memória
– Expoente negativo (Laurent)
– Expoente fracionário (soma de potências)
● Coeficientes podem ser objetos Stream, símbolos
do SymPy, etc.
● Objeto “x”
● Interpolação (polinômios de Lagrange)
In [9]: lagrange.poly([(1, 3), (3, 14), (45, 0)])
Out[9]: -2.89773 + 6.0303 * x - 0.132576 * x^2
In [9]: lagrange.poly([(1, 3), (3, 14), (45, 0)])
Out[9]: -2.89773 + 6.0303 * x - 0.132576 * x^2
In [8]: (x + x ** 2 + x ** -.5)(4)
Out[8]: 20.5
In [8]: (x + x ** 2 + x ** -.5)(4)
Out[8]: 20.5
23. Comparação de números em ponto
flutuante (IEEE 754)
● Valor absoluto (limiar “l”)
● Comparação pelo número de bits de mantissa (“t”
bits de tolerância para “s” bits de mantissa)
● Implementado em um dicionário de estratégias:
– almost_eq.diff
– almost_eq ou almost_eq.bits
∣a−b∣≤l
∣a−b∣≤2(t − s−1)∣a+b∣
24. Quantização em ponto flutuante
Com CascadeFilter
Warning:
Há textos que nem sempre são claros quanto aos aspectos
necessários à implementação para garantir a estabilidade numérica
freq = 100 * Hz
bw = erb(freq, Hz) * gammatone_erb_constants(4)[0]
filt = gammatone.slaney(freq, bw)
filt.plot(rate=rate, freq_scale="log", samples=8192).show()
freq = 100 * Hz
bw = erb(freq, Hz) * gammatone_erb_constants(4)[0]
filt = gammatone.slaney(freq, bw)
filt.plot(rate=rate, freq_scale="log", samples=8192).show()
Sem CascadeFilter
35. AbstractOperatorOverloaderMeta
● Metaclasse
– Classe cujas instâncias são classes
● Abstrata
– Classe com recursos especificados porém sem
implementação
● Sobrecarga massiva de operadores:
– Binários
– Binários reversos
– Unários
● Utiliza-se da classe OpMethod
– Organiza todos os 33 possíveis dunders de operadores
Há, entretanto, 35 no Python 2.
AbstractOperatorOverloaderMeta
insere __div__ e __rdiv__
automaticamente a partir de
__truediv__ e __rtruediv__
(quando aplicável)
36. Objeto window
Um dicionário de estratégias
In [1]: from audiolazy import window
In [2]: window # Vejamos as estratégias disponíveis
Out[2]:
{('bartlett',): <function audiolazy.lazy_analysis.bartlett>,
('blackman',): <function audiolazy.lazy_analysis.blackman>,
('hamming',): <function audiolazy.lazy_analysis.hamming>,
('hann', 'hanning'): <function audiolazy.lazy_analysis.hann>,
('rectangular', 'rect'): <function audiolazy.lazy_analysis.rectangular>,
('triangular', 'triangle'): <function audiolazy.lazy_analysis.triangular>}
In [3]: window["rect"](3) # Obtém a estratégia, chamando com 1 argumento
Out[3]: [1.0, 1.0, 1.0]
In [4]: window.triangle(3) # Idem, mas feito com outra sintaxe (dicionário)
Out[4]: [0.5, 1.0, 0.5]
In [5]: hm_wnd = window.hamming # Referenciando fora do dicionário
In [6]: hm_wnd # Esta estratégia é uma função comum
Out[6]: <function audiolazy.lazy_analysis.hamming>
In [1]: from audiolazy import window
In [2]: window # Vejamos as estratégias disponíveis
Out[2]:
{('bartlett',): <function audiolazy.lazy_analysis.bartlett>,
('blackman',): <function audiolazy.lazy_analysis.blackman>,
('hamming',): <function audiolazy.lazy_analysis.hamming>,
('hann', 'hanning'): <function audiolazy.lazy_analysis.hann>,
('rectangular', 'rect'): <function audiolazy.lazy_analysis.rectangular>,
('triangular', 'triangle'): <function audiolazy.lazy_analysis.triangular>}
In [3]: window["rect"](3) # Obtém a estratégia, chamando com 1 argumento
Out[3]: [1.0, 1.0, 1.0]
In [4]: window.triangle(3) # Idem, mas feito com outra sintaxe (dicionário)
Out[4]: [0.5, 1.0, 0.5]
In [5]: hm_wnd = window.hamming # Referenciando fora do dicionário
In [6]: hm_wnd # Esta estratégia é uma função comum
Out[6]: <function audiolazy.lazy_analysis.hamming>
Também é um iterável por
suas estratégias
(ver windows_plot.py)
37. Documentação
● Docstrings: documentação no código
– Uso em ambientes interativos
– reStructuredText
– Organização em seções
● Visualização no Spyder (Rich Text), IPython
● Sphinx
– Conversão automática do sistema de seções para o formato do
Sphinx
– Muitos formatos: LaTeX (PDF, DVI, PS), ePUB, HTML, TexInfo, etc.
● Apresentação, instruções de instalação e exemplos básicos
– Integração com MatPlotLib, Music21, wxPython, Tkinter, etc.
http://pythonhosted.org/audiolazy
https://github.com/danilobellini/audiolazy/tree/master/audiolazy/examples
39. AudioLazy
● DSP (Digital Signal Processing) para áudio
– Análise
● MIR (Music Information Retrieval)
– Síntese
– Processamento
● Expressividade de código
– Facilita prototipação, simulação
● Tempo real (latência de 35ms é possível)
– Possibilita uso em aplicações finais
● 100% Python
– Multiplataforma
Suporta Python 2 e 3 com o
mesmo código!
40. Motivações e justificativas
No papel...
● Demanda e insatisfação com código existente
– Por mim
● Vetores (NumPy, Octave/MatLab)...tempo real?
● Índices...=(
– Por outros
● Sustainable Software for Audio and Music Research
– ISMIR 2012 (Tutorial)
– DAFx 2012 (Tutorial)
● Software Carpentry
● Ausência de código fonte disponível
– Algoritmo de Klapuri (2008)!!! Bad guy...código?
● Base para trabalhos futuros
41. Resumidamente:
The Litmus Test
If you have to refer to theIf you have to refer to the
documentation every timedocumentation every time
you use a module, find (oryou use a module, find (or
build) a new module.build) a new module.
Até tentei evitar...mas...
43. Testes com oráculos
● 80 c/ o scipy.signal.lfilter
● 64 c/ o subpacote de otimização do SciPy
● 2 c/ o NumPy
import pytest
p = pytest.mark.parametrize
from scipy.signal import lfilter
from audiolazy import ZFilter, almost_eq
class TestZFilterScipy(object):
@p("a", [[1.], [3.], [1., 3.], [15., -17.2], [-18., 9.8, 0., 14.3]])
@p("b", [[1.], [-1.], [1., 0., -1.], [1., 3.]])
@p("data", [range(5), range(5, 0, -1), [7, 22, -5], [8., 3., 15.]])
def test_lfilter(self, a, b, data):
filt = ZFilter(b, a) # Cria um filtro com a AudioLazy
expected = lfilter(b, a, data).tolist() # Aplica o filtro com o SciPy
assert almost_eq(filt(data), expected) # Compara os resultados
import pytest
p = pytest.mark.parametrize
from scipy.signal import lfilter
from audiolazy import ZFilter, almost_eq
class TestZFilterScipy(object):
@p("a", [[1.], [3.], [1., 3.], [15., -17.2], [-18., 9.8, 0., 14.3]])
@p("b", [[1.], [-1.], [1., 0., -1.], [1., 3.]])
@p("data", [range(5), range(5, 0, -1), [7, 22, -5], [8., 3., 15.]])
def test_lfilter(self, a, b, data):
filt = ZFilter(b, a) # Cria um filtro com a AudioLazy
expected = lfilter(b, a, data).tolist() # Aplica o filtro com o SciPy
assert almost_eq(filt(data), expected) # Compara os resultados
44. Conclusões
● Tempo real em Python
● Expressividade + avaliação tardia
– Modularidade
● Multiparadigma
– Funcional
– Reflexivo
– Objetos
● Filtros digitais a partir de filtros analógicos
● Orientação a testes
● Padrão IEEE 754
45. Últimas novidades!
● Python 3!
● Contas com polinômios variantes no tempo
● Métodos das classes Stream e StreamTeeHub
– Peek
– Skip
– Limit
● Polinômios de Lagrange (interpolação)
● Reamostragem (taxa de amostragem variante no
tempo)
– Isso é útil? (variante no tempo)
46. Possíveis continuações para o
desenvolvimento da AudioLazy
● Análise
– FFT
– Heurísticas para transcrição
– Outras transformadas
● Modelagem audição
– Outros modelos (Lyon, Seneff,
gamma chirp, etc.)
– Conversão entre x-dB e ERB
● Síntese e processamento
– Wah, phaser, flanger, chorus, eco,
compressor, noise gate, limiter, …
● Testes
– 100%
– Mock MatPlotLib
●
Filtros
– Atrasos/expoentes variantes no
tempo
– Interpolação por splines
– Frações parciais
– Otimização
– Escrita no tempo
– SymPy
– Treliça
●
Outros
– Integrar com PureData
– I/O MIDI
– Plugins (e.g. Vamp)
– Tutorial
– Logo