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
(2013-10-16) [LatinoWare] Processamento de sinais em Python
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