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>
Parte 1
Síntese! Show me the code!
Go go go!Go go go!
Não, Python!Não, Python!
“Hello world” em áudio
● Tocar uma senóide
– Console interativo
– Script
from audiolazy import *
rate = 44100
s, Hz = sHz(rate)
th = AudioIO().play(sinusoid(440 * Hz), rate=rate)
from audiolazy import *
rate = 44100
s, Hz = sHz(rate)
th = AudioIO().play(sinusoid(440 * Hz), rate=rate)
from audiolazy import *
rate = 44100
s, Hz = sHz(rate)
with AudioIO(True) as player:
player.play(sinusoid(440 * Hz), rate=rate)
from audiolazy import *
rate = 44100
s, Hz = sHz(rate)
with AudioIO(True) as player:
player.play(sinusoid(440 * Hz), rate=rate)
Multithread!
Síntese
● ControlStream
– Property “value”
– Permite interatividade
● Tempo real
● Síntese
– Ring Modulation - Senóide * Senóide
– AM - Senóide * (1 + Senóide)
– FM - Senóide(Senóide)
– Subtrativa (e.g. Karplus-Strong)
– Aditiva (e.g. TableLookup) Imagem da
Wikipedia
Exemplos!!!wxPython,
Music21
Parte 2
Queremos mais itertools!!!
repeat chain count imap ifilter izip ...repeat chain count imap ifilter izip ...
Stream !Stream !
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!
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]
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]
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)
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]]
Parte 3
FiltrosFiltros
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)
Transformada Z
● Definição:
● Interpretação:
–Atrasoem k
amostras!
● Muitos “infinitos”
– Teoremas
● Possibilitam o uso prático
(eliminam os “infinitos”)
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)
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:
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)
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...
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
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?
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]
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
SymPy
Stream de símbolos
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∣
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
Parte 5
MIRMIR
((Music Information RetrievalMusic Information Retrieval))
Pitch – Shepard
● Som de Shepard
– Subir “sem parar”
– Exemplo no GitHub
● Duas dimensões:
– Altura (pitch height)
● Dimensão “linear”
– Croma (pitch chroma)
● Dimensão “circular”
● Lembra Escher →
Série harmônica
● F0, 2F0, 3F0, 4F0 …
– 100 Hz, 200 Hz, 300 Hz... Comb!
freqs = [str2freq(note) for note in "E2 G#2 B2".split()] # Mi maior
filt = ParallelFilter(comb.tau(freq_to_lag(freq * Hz), .1 * s)
for freq in freqs)
filt.plot(samples=8192, rate=rate, min_freq=220*Hz, max_freq=880*Hz).show()
freqs = [str2freq(note) for note in "E2 G#2 B2".split()] # Mi maior
filt = ParallelFilter(comb.tau(freq_to_lag(freq * Hz), .1 * s)
for freq in freqs)
filt.plot(samples=8192, rate=rate, min_freq=220*Hz, max_freq=880*Hz).show()
Inteiros?
Racionais?
Primos?
2+ oitava
ZCR
Taxa de cruzamentos no zero
In [15]: pitch1.take(10) # Resultado em Hz
Out[15]:
[872.0947265625001,
882.861328125,
872.0947265625001,
882.861328125,
882.861328125,
882.861328125,
882.861328125,
872.0947265625001,
882.861328125,
872.0947265625001]
In [16]: freq2str(pitch1).take(10) # Resultado em nomes de notas
Out[16]:
['A5+5.62%',
'A5+5.62%',
'A5+5.62%',
'A5+5.62%',
'A5+5.62%',
'A5-15.62%',
'A5+5.62%',
'A5-15.62%',
'A5+5.62%',
'A5+5.62%']
In [15]: pitch1.take(10) # Resultado em Hz
Out[15]:
[872.0947265625001,
882.861328125,
872.0947265625001,
882.861328125,
882.861328125,
882.861328125,
882.861328125,
872.0947265625001,
882.861328125,
872.0947265625001]
In [16]: freq2str(pitch1).take(10) # Resultado em nomes de notas
Out[16]:
['A5+5.62%',
'A5+5.62%',
'A5+5.62%',
'A5+5.62%',
'A5+5.62%',
'A5-15.62%',
'A5+5.62%',
'A5-15.62%',
'A5+5.62%',
'A5+5.62%']
pitch1 = zcross_pitch(data1) / Hzpitch1 = zcross_pitch(data1) / Hz
@tostream
def zcross_pitch(sig, size=2048):
"Devolve a altura em cada bloco com o dado tamanho"
for blk in zcross(sig, hysteresis=.2).blocks(size):
yield lag_to_freq(2. * size / sum(blk))
@tostream
def zcross_pitch(sig, size=2048):
"Devolve a altura em cada bloco com o dado tamanho"
for blk in zcross(sig, hysteresis=.2).blocks(size):
yield lag_to_freq(2. * size / sum(blk))
data1 = .5 * sinusoid(880 * Hz)
data1 += .5 * saw_table(880*3 * Hz)
data1 *= .9 + .1 * white_noise()
data1 = .5 * sinusoid(880 * Hz)
data1 += .5 * saw_table(880*3 * Hz)
data1 *= .9 + .1 * white_noise()
Por quenão DFT?
AMDF (Average Magnitude Difference
Function)
Autocorrelação
Transcrição por envoltória dinâmica
Decomposição cromática
● Filtros gammatone
– Paralelo
● Oitavas
In [1]: from audiolazy import octaves
In [2]: octaves(440)
Out[2]: [27.5, 55.0, 110.0, 220.0, 440, 880, 1760, 3520, 7040, 14080]
In [1]: from audiolazy import octaves
In [2]: octaves(440)
Out[2]: [27.5, 55.0, 110.0, 220.0, 440, 880, 1760, 3520, 7040, 14080]
from __future__ import division
from audiolazy import *
def cromafb(classes=12, rate):
s, Hz = sHz(rate)
cg = gammatone_erb_constants(4)[0]
fb = 440
return [
ParallelFilter(
gammatone.sampled(f*Hz, cg*erb(f))
for f in octaves(fb * 2**(n/classes))
) for n in xrange(classes)
]
rate = 44100
bank = cromafb(rate=rate)
bank[0].plot(freq_scale="log", rate=rate)
from __future__ import division
from audiolazy import *
def cromafb(classes=12, rate):
s, Hz = sHz(rate)
cg = gammatone_erb_constants(4)[0]
fb = 440
return [
ParallelFilter(
gammatone.sampled(f*Hz, cg*erb(f))
for f in octaves(fb * 2**(n/classes))
) for n in xrange(classes)
]
rate = 44100
bank = cromafb(rate=rate)
bank[0].plot(freq_scale="log", rate=rate)
Cromagrama
from audiolazy import *
rate = 22050
s, Hz = sHz(rate)
size = 512
table = sin_table.harmonize({1: 1, 2: 5, 3: 3, 4: 2, 6: 9, 8: 1}).normalize()
data = table(str2freq("Bb3") * Hz).take(size) # Nota si bemol da 3a oitava
filt = lpc(data, order=14) # Filtro de análise
G = 1e-2 # Ganho apenas para alinhamento na visualização com a DFT
# Filtro de síntese
(G / filt).plot(blk=data, rate=rate, samples=1024, unwrap=False).show()
from audiolazy import *
rate = 22050
s, Hz = sHz(rate)
size = 512
table = sin_table.harmonize({1: 1, 2: 5, 3: 3, 4: 2, 6: 9, 8: 1}).normalize()
data = table(str2freq("Bb3") * Hz).take(size) # Nota si bemol da 3a oitava
filt = lpc(data, order=14) # Filtro de análise
G = 1e-2 # Ganho apenas para alinhamento na visualização com a DFT
# Filtro de síntese
(G / filt).plot(blk=data, rate=rate, samples=1024, unwrap=False).show()
Envoltória espectral
LPC - Predição Linear
Formantes
Pode ser
utilizado para
classificação
de vogais
Parte 6
NúcleoNúcleo
(Não, não vai explodir...)(Não, não vai explodir...)
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)
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)
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
Parte 7
FinalizaçãoFinalização
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!
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
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...
Resultados cobertura de código
(Testes automatizados)
--------------- coverage: platform linux2, python 2.7.3-final-0 ----------------
Name Stmts Miss Branch BrPart Cover Missing
------------------------------------------------------------
__init__ 44 1 18 4 92% 72
lazy_analysis 125 3 60 2 97% 211, 259-260
lazy_auditory 60 0 14 0 100%
lazy_compat 42 5 6 1 88% 43, 67-68, 78-79
lazy_core 174 7 78 9 94% 124, 136-138, 346, 471, 478
lazy_filters 509 179 245 117 61% 55, 62, 83, 93, [...]
lazy_io 156 43 58 28 67% 89, 141-157, 161, [...]
lazy_itertools 37 12 20 13 56% 38, 59-60, 69-74, 100-103
lazy_lpc 128 15 42 7 87% 121, 135-136, [...]
lazy_math 61 1 28 0 99% 133
lazy_midi 54 5 26 3 90% 70, 111, 150, 156, 158
lazy_misc 110 9 62 10 89% 156-157, 194, [...]
lazy_poly 184 2 124 3 98% 387-388
lazy_stream 175 2 76 4 98% 59, 738
lazy_synth 243 32 118 39 80% 277-299, 428, 430, [...]
lazy_text 104 16 70 18 80% 133-148, 207, [...]
------------------------------------------------------------
TOTAL 2206 332 1045 258 82%
========================= 5938 passed in 29.00 seconds =========================
--------------- coverage: platform linux2, python 2.7.3-final-0 ----------------
Name Stmts Miss Branch BrPart Cover Missing
------------------------------------------------------------
__init__ 44 1 18 4 92% 72
lazy_analysis 125 3 60 2 97% 211, 259-260
lazy_auditory 60 0 14 0 100%
lazy_compat 42 5 6 1 88% 43, 67-68, 78-79
lazy_core 174 7 78 9 94% 124, 136-138, 346, 471, 478
lazy_filters 509 179 245 117 61% 55, 62, 83, 93, [...]
lazy_io 156 43 58 28 67% 89, 141-157, 161, [...]
lazy_itertools 37 12 20 13 56% 38, 59-60, 69-74, 100-103
lazy_lpc 128 15 42 7 87% 121, 135-136, [...]
lazy_math 61 1 28 0 99% 133
lazy_midi 54 5 26 3 90% 70, 111, 150, 156, 158
lazy_misc 110 9 62 10 89% 156-157, 194, [...]
lazy_poly 184 2 124 3 98% 387-388
lazy_stream 175 2 76 4 98% 59, 738
lazy_synth 243 32 118 39 80% 277-299, 428, 430, [...]
lazy_text 104 16 70 18 80% 133-148, 207, [...]
------------------------------------------------------------
TOTAL 2206 332 1045 258 82%
========================= 5938 passed in 29.00 seconds =========================
Mock
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
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
Ú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)
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
Obrigado!
Perguntas?Perguntas?
Fork me on GitHubFork me on GitHub
https://github.com/danilobellini/audiolazyhttps://github.com/danilobellini/audiolazy

(2013-05-20) [DevInSampa] AudioLazy - DSP expressivo e em tempo real para o Python

  • 1.
    AudioLazy - DSPexpressivo 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>
  • 2.
    Parte 1 Síntese! Showme the code! Go go go!Go go go! Não, Python!Não, Python!
  • 3.
    “Hello world” emáudio ● Tocar uma senóide – Console interativo – Script from audiolazy import * rate = 44100 s, Hz = sHz(rate) th = AudioIO().play(sinusoid(440 * Hz), rate=rate) from audiolazy import * rate = 44100 s, Hz = sHz(rate) th = AudioIO().play(sinusoid(440 * Hz), rate=rate) from audiolazy import * rate = 44100 s, Hz = sHz(rate) with AudioIO(True) as player: player.play(sinusoid(440 * Hz), rate=rate) from audiolazy import * rate = 44100 s, Hz = sHz(rate) with AudioIO(True) as player: player.play(sinusoid(440 * Hz), rate=rate) Multithread!
  • 4.
    Síntese ● ControlStream – Property“value” – Permite interatividade ● Tempo real ● Síntese – Ring Modulation - Senóide * Senóide – AM - Senóide * (1 + Senóide) – FM - Senóide(Senóide) – Subtrativa (e.g. Karplus-Strong) – Aditiva (e.g. TableLookup) Imagem da Wikipedia Exemplos!!!wxPython, Music21
  • 5.
    Parte 2 Queremos maisitertools!!! repeat chain count imap ifilter izip ...repeat chain count imap ifilter izip ... Stream !Stream !
  • 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 convertidosem 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]]
  • 11.
  • 12.
    Filtros LTI (Lineares einvariantes 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 transformadaZ ● 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édiamó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 sobrefiltros ● 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 (exemplono 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 paraos 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
  • 22.
  • 23.
    Comparação de númerosem 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 pontoflutuante 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
  • 25.
    Parte 5 MIRMIR ((Music InformationRetrievalMusic Information Retrieval))
  • 26.
    Pitch – Shepard ●Som de Shepard – Subir “sem parar” – Exemplo no GitHub ● Duas dimensões: – Altura (pitch height) ● Dimensão “linear” – Croma (pitch chroma) ● Dimensão “circular” ● Lembra Escher →
  • 27.
    Série harmônica ● F0,2F0, 3F0, 4F0 … – 100 Hz, 200 Hz, 300 Hz... Comb! freqs = [str2freq(note) for note in "E2 G#2 B2".split()] # Mi maior filt = ParallelFilter(comb.tau(freq_to_lag(freq * Hz), .1 * s) for freq in freqs) filt.plot(samples=8192, rate=rate, min_freq=220*Hz, max_freq=880*Hz).show() freqs = [str2freq(note) for note in "E2 G#2 B2".split()] # Mi maior filt = ParallelFilter(comb.tau(freq_to_lag(freq * Hz), .1 * s) for freq in freqs) filt.plot(samples=8192, rate=rate, min_freq=220*Hz, max_freq=880*Hz).show() Inteiros? Racionais? Primos? 2+ oitava
  • 28.
    ZCR Taxa de cruzamentosno zero In [15]: pitch1.take(10) # Resultado em Hz Out[15]: [872.0947265625001, 882.861328125, 872.0947265625001, 882.861328125, 882.861328125, 882.861328125, 882.861328125, 872.0947265625001, 882.861328125, 872.0947265625001] In [16]: freq2str(pitch1).take(10) # Resultado em nomes de notas Out[16]: ['A5+5.62%', 'A5+5.62%', 'A5+5.62%', 'A5+5.62%', 'A5+5.62%', 'A5-15.62%', 'A5+5.62%', 'A5-15.62%', 'A5+5.62%', 'A5+5.62%'] In [15]: pitch1.take(10) # Resultado em Hz Out[15]: [872.0947265625001, 882.861328125, 872.0947265625001, 882.861328125, 882.861328125, 882.861328125, 882.861328125, 872.0947265625001, 882.861328125, 872.0947265625001] In [16]: freq2str(pitch1).take(10) # Resultado em nomes de notas Out[16]: ['A5+5.62%', 'A5+5.62%', 'A5+5.62%', 'A5+5.62%', 'A5+5.62%', 'A5-15.62%', 'A5+5.62%', 'A5-15.62%', 'A5+5.62%', 'A5+5.62%'] pitch1 = zcross_pitch(data1) / Hzpitch1 = zcross_pitch(data1) / Hz @tostream def zcross_pitch(sig, size=2048): "Devolve a altura em cada bloco com o dado tamanho" for blk in zcross(sig, hysteresis=.2).blocks(size): yield lag_to_freq(2. * size / sum(blk)) @tostream def zcross_pitch(sig, size=2048): "Devolve a altura em cada bloco com o dado tamanho" for blk in zcross(sig, hysteresis=.2).blocks(size): yield lag_to_freq(2. * size / sum(blk)) data1 = .5 * sinusoid(880 * Hz) data1 += .5 * saw_table(880*3 * Hz) data1 *= .9 + .1 * white_noise() data1 = .5 * sinusoid(880 * Hz) data1 += .5 * saw_table(880*3 * Hz) data1 *= .9 + .1 * white_noise() Por quenão DFT?
  • 29.
    AMDF (Average MagnitudeDifference Function)
  • 30.
  • 31.
    Decomposição cromática ● Filtrosgammatone – Paralelo ● Oitavas In [1]: from audiolazy import octaves In [2]: octaves(440) Out[2]: [27.5, 55.0, 110.0, 220.0, 440, 880, 1760, 3520, 7040, 14080] In [1]: from audiolazy import octaves In [2]: octaves(440) Out[2]: [27.5, 55.0, 110.0, 220.0, 440, 880, 1760, 3520, 7040, 14080] from __future__ import division from audiolazy import * def cromafb(classes=12, rate): s, Hz = sHz(rate) cg = gammatone_erb_constants(4)[0] fb = 440 return [ ParallelFilter( gammatone.sampled(f*Hz, cg*erb(f)) for f in octaves(fb * 2**(n/classes)) ) for n in xrange(classes) ] rate = 44100 bank = cromafb(rate=rate) bank[0].plot(freq_scale="log", rate=rate) from __future__ import division from audiolazy import * def cromafb(classes=12, rate): s, Hz = sHz(rate) cg = gammatone_erb_constants(4)[0] fb = 440 return [ ParallelFilter( gammatone.sampled(f*Hz, cg*erb(f)) for f in octaves(fb * 2**(n/classes)) ) for n in xrange(classes) ] rate = 44100 bank = cromafb(rate=rate) bank[0].plot(freq_scale="log", rate=rate)
  • 32.
  • 33.
    from audiolazy import* rate = 22050 s, Hz = sHz(rate) size = 512 table = sin_table.harmonize({1: 1, 2: 5, 3: 3, 4: 2, 6: 9, 8: 1}).normalize() data = table(str2freq("Bb3") * Hz).take(size) # Nota si bemol da 3a oitava filt = lpc(data, order=14) # Filtro de análise G = 1e-2 # Ganho apenas para alinhamento na visualização com a DFT # Filtro de síntese (G / filt).plot(blk=data, rate=rate, samples=1024, unwrap=False).show() from audiolazy import * rate = 22050 s, Hz = sHz(rate) size = 512 table = sin_table.harmonize({1: 1, 2: 5, 3: 3, 4: 2, 6: 9, 8: 1}).normalize() data = table(str2freq("Bb3") * Hz).take(size) # Nota si bemol da 3a oitava filt = lpc(data, order=14) # Filtro de análise G = 1e-2 # Ganho apenas para alinhamento na visualização com a DFT # Filtro de síntese (G / filt).plot(blk=data, rate=rate, samples=1024, unwrap=False).show() Envoltória espectral LPC - Predição Linear Formantes Pode ser utilizado para classificação de vogais
  • 34.
    Parte 6 NúcleoNúcleo (Não, nãovai explodir...)(Não, não vai explodir...)
  • 35.
    AbstractOperatorOverloaderMeta ● Metaclasse – Classecujas 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áriode 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çãono 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
  • 38.
  • 39.
    AudioLazy ● DSP (DigitalSignal 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 Nopapel... ● 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 Ifyou 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...
  • 42.
    Resultados cobertura decódigo (Testes automatizados) --------------- coverage: platform linux2, python 2.7.3-final-0 ---------------- Name Stmts Miss Branch BrPart Cover Missing ------------------------------------------------------------ __init__ 44 1 18 4 92% 72 lazy_analysis 125 3 60 2 97% 211, 259-260 lazy_auditory 60 0 14 0 100% lazy_compat 42 5 6 1 88% 43, 67-68, 78-79 lazy_core 174 7 78 9 94% 124, 136-138, 346, 471, 478 lazy_filters 509 179 245 117 61% 55, 62, 83, 93, [...] lazy_io 156 43 58 28 67% 89, 141-157, 161, [...] lazy_itertools 37 12 20 13 56% 38, 59-60, 69-74, 100-103 lazy_lpc 128 15 42 7 87% 121, 135-136, [...] lazy_math 61 1 28 0 99% 133 lazy_midi 54 5 26 3 90% 70, 111, 150, 156, 158 lazy_misc 110 9 62 10 89% 156-157, 194, [...] lazy_poly 184 2 124 3 98% 387-388 lazy_stream 175 2 76 4 98% 59, 738 lazy_synth 243 32 118 39 80% 277-299, 428, 430, [...] lazy_text 104 16 70 18 80% 133-148, 207, [...] ------------------------------------------------------------ TOTAL 2206 332 1045 258 82% ========================= 5938 passed in 29.00 seconds ========================= --------------- coverage: platform linux2, python 2.7.3-final-0 ---------------- Name Stmts Miss Branch BrPart Cover Missing ------------------------------------------------------------ __init__ 44 1 18 4 92% 72 lazy_analysis 125 3 60 2 97% 211, 259-260 lazy_auditory 60 0 14 0 100% lazy_compat 42 5 6 1 88% 43, 67-68, 78-79 lazy_core 174 7 78 9 94% 124, 136-138, 346, 471, 478 lazy_filters 509 179 245 117 61% 55, 62, 83, 93, [...] lazy_io 156 43 58 28 67% 89, 141-157, 161, [...] lazy_itertools 37 12 20 13 56% 38, 59-60, 69-74, 100-103 lazy_lpc 128 15 42 7 87% 121, 135-136, [...] lazy_math 61 1 28 0 99% 133 lazy_midi 54 5 26 3 90% 70, 111, 150, 156, 158 lazy_misc 110 9 62 10 89% 156-157, 194, [...] lazy_poly 184 2 124 3 98% 387-388 lazy_stream 175 2 76 4 98% 59, 738 lazy_synth 243 32 118 39 80% 277-299, 428, 430, [...] lazy_text 104 16 70 18 80% 133-148, 207, [...] ------------------------------------------------------------ TOTAL 2206 332 1045 258 82% ========================= 5938 passed in 29.00 seconds ========================= Mock
  • 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 realem 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! ● Python3! ● 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 parao 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
  • 47.
    Obrigado! Perguntas?Perguntas? Fork me onGitHubFork me on GitHub https://github.com/danilobellini/audiolazyhttps://github.com/danilobellini/audiolazy