O documento apresenta uma introdução ao processamento de sinais em Python com ênfase em aplicações de áudio. Ele discute conceitos como representação de áudio, filtros digitais, síntese e controle de áudio em tempo real usando a biblioteca AudioLazy.
(2013-07-05) [fisl] Semáforo Gráfico dose para TDD em dojos
Processamento de sinais em Python com AudioLazy
1. Processamento de sinais em Python
Uma introdução com ênfase em aplicações em áudio
Danilo de Jesus da Silva Bellini (AudioLazy Developer)
danilo.bellini [at] gmail.com – Twitter: @danilobellini
https://pypi.python.org/pypi/audiolazy
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
2. ●
c/ Pyth
o m on
AudioLazy
es
2e
mo
có 3
dig
DSP (Digital Signal Processing) para áudio
o
–
Análise
●
MIR (Music Information Retrieval)
–
–
●
Síntese
Processamento
Expressividade de código
–
●
Documentação (Sphinx)
http://pythonhosted.org/audiolazy
Facilita prototipação, simulação
Tempo real (latência de aproximadamente 35ms c/ o Jack)
–
Possibilita uso em aplicações finais
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
!
4. Parte 1
Como funciona um teclado
sintetizador?
Talk is cheap.
Show me the code.
(Linus Torvalds)
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
5. “Hello world” em áudio
●
Tocar uma senóide
–
Console
from audiolazy import *
from audiolazy import *
(e.g. IPython)
rate = 44100
rate = 44100
s, Hz = sHz(rate)
s, Hz = sHz(rate)
Multith
rea
d!
player = AudioIO()
player = AudioIO()
snd = sinusoid(440 * Hz).limit(2 * s)
snd = sinusoid(440 * Hz).limit(2 * s)
th = player.play(snd, rate=rate) # an AudioThread
th = player.play(snd, rate=rate) # an AudioThread
player.close() # Kill th (AudioIO arg isn't true)
player.close() # Kill th (AudioIO arg isn't true)
–
Scripts
●
Gerenciadores de contexto
with AudioIO(True)
with AudioIO(True)
player.play(snd,
player.play(snd,
as player: # Wait threads
as player: # Wait threads
rate=rate)
rate=rate)
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
6. Notas/Alturas e MIDI Pitch
●
Pitch (notas/alturas)
–
D = Ré
–
F = Fá
–
G = Sol
–
A = Lá
–
B = Si
–
–
●
“Cb4” (dó bemol) é a mesma nota que B3
MIDI Pitch
–
Define 69 como A4 (lá central), deslocamento em
semitons
freq2str
–
Ignoram a alteração
str2midi
–
Iniciam em dó
midi2str
–
Oitavas
–
●
Funções para realizar
conversões
E = Mi
–
●
C = Dó
–
●
str2freq
–
midi2freq
–
freq2midi
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
7. Controle e tipos de síntese
●
ControlStream
In [1]: data = ControlStream(42)
In [1]: data = ControlStream(42)
–
Property “value”
In [2]: data.take(5)
In [2]: data.take(5)
Out[2]: [42, 42, 42, 42, 42]
Out[2]: [42, 42, 42, 42, 42]
–
Permite interatividade
In [3]: data.value = -1
In [3]: data.value = -1
●
●
Tempo real
Síntese
In [4]:
In [4]:
Out[4]:
Out[4]:
–
Aditiva (e.g. classe TableLookup)
–
data.take(5)
data.take(5)
[-1, -1, -1, -1, -1]
[-1, -1, -1, -1, -1]
Modulação
●
Ring Modulation (Anel)
–
●
AM (Amplitude)
–
●
Senóide * (1 + Senóide)
FM (Frequência ou fase)
–
–
Senóide * Senóide
Senóide(Senóide)
Ex
“ge em
tch plo
wx ”, tk s!!!
Py in
mu tho ter,
sic n,
21
Subtrativa (e.g. modelo de Karplus-Strong)
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
8. Parte 2
Funcionamento do altofalante e
Representação do áudio
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
9. Container para áudio
●
Tempo real
–
Amostras (dados/elementos) inexistentes...
●
●
Ocorre em áudio e
em vídeos, mas
não em imagens
estáticas
...em tempo de compilação (dados a serem coletados)
...em tempo de execução (dados criados no futuro)
–
Duração possivelmente indefinida (endless)
–
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
Laziness!
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
10. Classe Stream
In [1]: data = Stream(range(7))
In [1]: data = Stream(range(7))
●
Iterável
In [2]: blks = data.blocks(size=3, hop=2)
In [2]: blks = data.blocks(size=3, hop=2)
●
Heterogêneo
In [3]: [list(blk) for blk in blks]
In [3]: [list(blk) for blk in blks]
Out[3]: [[0, 1, 2], [2, 3, 4], [4, 5, 6]]
Out[3]: [[0, 1, 2], [2, 3, 4], [4, 5, 6]]
●
Lazy! (Avaliação tardia)
●
Operadores (Elementwise/broadcast)
●
Métodos (take, peek, limit, skip, map, filter, blocks)
In
In
In
In
In
In
[1]:
[1]:
[2]:
[2]:
[3]:
[3]:
from audiolazy import Stream, inf
from audiolazy import Stream, inf
dados = Stream(5, 7, 1, 2, 5, 3, 2) # Periódico
dados = Stream(5, 7, 1, 2, 5, 3, 2) # Periódico
dados2 = Stream(0, 1) # Idem
dados2 = Stream(0, 1) # Idem
In [4]:
In [4]:
Out[4]:
Out[4]:
(dados
(dados
[5, 8,
[5, 8,
+ dados2).take(15)
+ dados2).take(15)
1, 3, 5, 4, 2, 6, 7, 2, 2, 6, 3, 3, 5]
1, 3, 5, 4, 2, 6, 7, 2, 2, 6, 3, 3, 5]
In [5]:
In [5]:
Out[5]:
Out[5]:
(_ * Stream(1 + 2j, -3j, 7).imag).map(int).take(inf)
(_ * Stream(1 + 2j, -3j, 7).imag).map(int).take(inf)
[2.0, 0.0, 14]
[2.0, 0.0, 14]
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
11. Parte 3
Efeito “wah”, knob guitarra, transições
Filtros digitais e a Transformada Z
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
12. Filtros LTI
(Lineares e invariantes no tempo)
“Digital signal processing is mainly
based on linear time-invariant
systems.”
systems.”
(Dutilleux, Dempwolf, Holters e Zölzer
DAFx, segunda edição, capítulo 4, p. 103)
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
13. Transformada Z
●
Definição:
●
Interpretação:
Atraso em k
amostras!
In [1]: from audiolazy import z, inf
In [1]: from audiolazy import z, inf
In [2]: sig = [1, 2, 3, 4, 5, 6, 7]
In [2]: sig = [1, 2, 3, 4, 5, 6, 7]
In [3]:
In [3]:
Out[3]:
Out[3]:
(z **
(z **
[0.0,
[0.0,
-2)(sig).take(inf)
-2)(sig).take(inf)
0.0, 1, 2, 3, 4, 5]
0.0, 1, 2, 3, 4, 5]
In [4]:
In [4]:
Out[4]:
Out[4]:
(1 - z ** -2)(sig).take(inf)
(1 - z ** -2)(sig).take(inf)
[1.0, 2.0, 2, 2, 2, 2, 2]
[1.0, 2.0, 2, 2, 2, 2, 2]
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
14. Objeto “z”
In [1]: from audiolazy import z, Stream, maverage
In [1]: from audiolazy import z, Stream, maverage
In [2]: M = 5
In [2]: M = 5
In [3]: media_movel_5 = (1 - z ** -M) / (M * (1 - z ** -1))
In [3]: media_movel_5 = (1 - z ** -M) / (M * (1 - z ** -1))
In [4]: acumulador = 1 / (1 - z ** -1)
In [4]: acumulador = 1 / (1 - z ** -1)
In [5]:
In [5]:
Out[5]:
Out[5]:
media_movel_5(Stream(5)).take(10)
media_movel_5(Stream(5)).take(10)
[1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0]
[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)
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]
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)
In [7]: maverage.recursive(4)
Out[7]:
Out[7]:
0.25 - 0.25 * z^-4
0.25 - 0.25 * z^-4
----------------------------------1 - z^-1
1 - z^-1
Filtros LTI, em geral:
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
15. Filtros prontos!
●
AudioLazy
Filtr
Coe os varia
ntes
parc ficien
no t
tes (
elas
e mp
fato
“a *
r
o
obje z ** -k” es “a” e !
tos S ) po
m
trea dem s
m)
er
–
Média móvel
–
Ressonadores
–
Comb
–
Passa-baixas
–
Passa-altas
–
●
Gammatone (Patterson-Holdsworth, audição)
Scipy.signal
–
Butterworth
–
Chebyshev
–
JIT!
Elíptico
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
16. Plot (AudioLazy + MatPlotLib)!
●
DTFT - Caso particular da transformada Z
–
●
Método plot dos filtros
–
●
O valor de z está na circunferência complexa unitária
Resposta em frequência
Método zplot
–
Estabilidade do filtro
–
X
Pólos: “X”
●
–
Raízes do denominador
X
Zeros: “O”
●
Raízes do numerador
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
17. Parte 4
Análise
MIR
(Music Information Retrieval)
Retrieval)
O que é “altura” / “pitch”?
Como um afinador de guitarra funciona?
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
18. Pitch – Shepard
●
●
Exemplo no GitHub
Cientista propõe dividir
o “pitch” em duas
dimensões:
–
Altura (pitch height)
●
–
Croma (pitch chroma)
●
●
●
Dimensão “linear”
Dimensão “circular”
Lembra Escher →
“Hélice”
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
19. Série harmônica
●
F0, 2F0, 3F0, 4F0 …
–
100 Hz, 200 Hz, 300 Hz...
Inteiros?
Racionais?
Primos?
2+
oitava
Comb!
freqs = [str2freq(note) for note in "E2 G#2 B2".split()] # Mi maior
freqs = [str2freq(note) for note in "E2 G#2 B2".split()] # Mi maior
filt = ParallelFilter(comb.tau(freq_to_lag(freq * Hz), .1 * s)
filt = ParallelFilter(comb.tau(freq_to_lag(freq * Hz), .1 * s)
for freq in freqs)
Foz do for freqPR –freqs)
Iguaçu – in Processamento de sinais em Python
filt.plot(samples=8192, rate=rate, min_freq=220*Hz, max_freq=880*Hz).show()
filt.plot(samples=8192, rate=rate, min_freq=220*Hz, max_freq=880*Hz).show()
2013-10-16 – Danilo J. S. Bellini – @danilobellini
20. Coletando a altura
●
ZCR (Taxa de cruzamento no zero)
●
DFT (Transformada Discreta de Fourier)
●
AMDF (Average Magnitude Difference Function)
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
22. Decomposição cromática
from __future__ import division
from __future__ import division
from audiolazy import *
from audiolazy import *
def cromafb(classes=12, rate):
def cromafb(classes=12, rate):
s, Hz = sHz(rate)
s, Hz = sHz(rate)
cg = gammatone_erb_constants(4)[0]
cg = gammatone_erb_constants(4)[0]
fb = 440
fb = 440
return [
return [
ParallelFilter(
ParallelFilter(
gammatone.sampled(f*Hz, cg*erb(f))
gammatone.sampled(f*Hz, cg*erb(f))
for f in octaves(fb * 2**(n/classes))
for f in octaves(fb * 2**(n/classes))
) for n in xrange(classes)
) for n in xrange(classes)
]
]
●
Filtros gammatone
–
●
Paralelo
rate = 44100
rate = 44100
bank = cromafb(rate=rate)
bank = cromafb(rate=rate)
bank[0].plot(freq_scale="log", rate=rate)
bank[0].plot(freq_scale="log", rate=rate)
Oitavas
In [1]: from audiolazy import octaves
In [1]: from audiolazy import octaves
In [2]: octaves(440)
In [2]: octaves(440) Iguaçu – PR – Processamento de sinais em Python
Foz do
Out[2]: [27.5, 55.0, 110.0, 220.0, 440, 880, 1760, 3520, 7040, 14080]
Out[2]: [27.5, 55.0, 110.0, 220.0, 440, 880, 1760, 3520, 7040, 14080]
2013-10-16 – Danilo J. S. Bellini – @danilobellini
23. Cromagrama
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
24. Envoltória espectral
LPC - Predição Linear
Formantes
Pode ser
utilizado para
classificação
de vogais
from audiolazy import *
from audiolazy import *
rate = 22050
rate = 22050
s, Hz = sHz(rate)
s, Hz = sHz(rate)
size = 512
size = 512
table = sin_table.harmonize({1: 1, 2: 5, 3: 3, 4: 2, 6: 9, 8: 1}).normalize()
table = sin_table.harmonize({1: 1, 2: 5, 3: 3, 4: 2, 6: 9, 8: 1}).normalize()
data = table(str2freq("Bb3")
data = table(str2freq("Bb3")
filt = lpc(data, order=14) #
filt = lpc(data, order=14) #
G = 1e-2 # Ganho apenas para
G = 1e-2 # Ganho apenas para
* Hz).take(size) # Nota si bemol da 3a oitava
* Hz).take(size) # Nota si bemol da 3a oitava
Filtro de análise
Filtro de análise
alinhamento na visualização com a DFT
alinhamento na visualização com a DFT
# Filtro de síntese
# Filtro de síntese do Iguaçu – PR – Processamento de sinais em Python
Foz
(G / filt).plot(blk=data, rate=rate, samples=1024, unwrap=False).show()
(G / filt).plot(blk=data, rate=rate, samples=1024, @danilobellini
2013-10-16 – Danilo J. S. Bellini – unwrap=False).show()
26. Fibonacci
●
h[0] = 0
●
h[1] = 1
●
h[n] = h[n-1] + h[n-2]
–
●
Entrada [0, 1, 0, 0, 0, 0, ...] aplicado a esse filtro digital
Função impulse()
h [n]=h[n−1]+ h[ n−2]+ δ[ n−1]
In [2]: (z ** -1 / (1 In [2]: (z ** -1 / (1 ...:
...:
Out[2]: [0, 1, 1, 2, 3,
Out[2]: [0, 1, 1, 2, 3,
z ** -1 - z ** -2))(impulse(zero=0,
z ** -1 - z ** -2))(impulse(zero=0,
one=1),
one=1),
5, 8, 13, 21, 34, 55, 89, 144, 233,
5, 8, 13, 21, 34, 55, 89, 144, 233,
zero=0).take(17)
zero=0).take(17)
377, 610, 987]
377, 610, 987]
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
27. Polinômios
●
Baseados em dicionário
In [7]: (x + x ** 2 + x ** -.5)(4)
In [7]: (x + x ** 2 + x ** -.5)(4)
Out[7]: 20.5
Out[7]: 20.5
–
Memória
–
Expoente negativo (Laurent)
–
Expoente fracionário (soma de potências)
●
Objeto “x”
●
Interpolação
–
Lagrange
In [9]:
In [9]:
Out[9]:
Out[9]:
lagrange.poly([(0, 0), (1, 1)])
lagrange.poly([(0, 0), (1, 1)])
x
x
In [10]: lagrange.poly([(0, 0), (1, 1), (2, 2)])
In [10]: lagrange.poly([(0, 0), (1, 1), (2, 2)])
Out[10]: x
Out[10]: x
In [11]: lagrange.poly([(0, 0), (1, 1), (2, 4)])
In [11]: lagrange.poly([(0, 0), (1, 1), (2, 4)])
Out[11]: x^2
Out[11]: x^2
In [9]: lagrange.poly([(1, 3), (3, 14), (45, 0)])
In [9]: lagrange.poly([(1, 3), (3, 14), (45, 0)])
Out[9]: -2.89773 + 6.0303 * x - 0.132576 * x^2
Out[9]: -2.89773 + 6.0303 * x - 0.132576 * x^2
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
28. Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
29. Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
30. Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
31. Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
32. Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
33. Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
34. Licenças
●
Linguagem
–
●
Python (CPython) – PSFL
Processamento de Sinais
–
–
PyAudio (reprodução e gravação de áudio) – MIT
–
MatPlotLib (visualização gráfica) – PSFL
–
NumPy (FFT, álgebra linear) – BSD 3-Clause
–
●
AudioLazy (análise, síntese e processamento) – GPLv3
SciPy (signal) – BSD 3-Clause
GUI
–
–
[Tcl/]Tk – BSD
–
●
wxPython/wxWidgets – wxWindows
Tkinter – PSFL
Outros
–
py.test (testes automatizados) – MIT
–
music21 (musicologia) – LGPLv3+
–
Sphinx (documentação) – BSD
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini
35. Obrigado!
Perguntas?
Fork me on GitHub
https://github.com/danilobellini/audiolazy
Foz do Iguaçu – PR – Processamento de sinais em Python
2013-10-16 – Danilo J. S. Bellini – @danilobellini