Terceiro Trabalho (menor)
Terceiro Trabalho (menor, para nota)
Entrega 2a-feira dia 20/05 antes da aula (15:55)
Neste trabalho nós implementaremos uma interface gráfica para o filtro de dois pólos e dois zeros que está implementado no patch Exemplos_Filtros.pd disponível no PACA. Aquele patch recebe mensagens com rótulos a0, a1, a2, b1 e b2 para definir diretamente os coeficientes da equação do filtro na forma
y[n] = a0*x[n]+a1*x[n-1]+a2*x[n-2]+b1*y[n-1]+b2*y[n-2].
Nossa interface gráfica permitirá a manipulação de 4 elementos gráficos, correspondentes a 2 pólos P1 e P2 e 2 zeros Z1 e Z2, em um quadrado que representará o trecho do plano complexo com coordenadas -1≤x≤+1 e -1≤y≤+1. Além destes, teremos um slider para controle de a0, que representa o ganho global do filtro. Para cada alteração na posição de algum pólo ou zero (ou do a0), o patch deve recomputar os coeficientes da equação do filtro acima, e enviar as mensagens correspondentes para o patch Exemplos_Filtros.pd. Para o correto funcionamento desta interface, discutiremos alguns detalhes do objeto gráfico [cnv], e também algumas restrições para o posicionamento dos pólos e zeros.
Objeto Canvas
O objeto [cnv] (Canvas, ou Inserir->Tela no menu do Pd) permite criar um elemento gráfico dentro do patch, na forma de um retângulo, que é reposicionável quando o patch está no modo de edição. Os atributos do Canvas podem ser definidos no ato da criação do objeto (veja o help) ou então através do menu Propriedades (clicando no canto superior esquerdo do Canvas). Estes atributos são a dimensão da área de seleção (quadrado no canto superior esquerdo que você pode selecionar e arrastar com o mouse), a dimensão do retângulo visível (área correspondente ao plano de fundo), os rótulos para comunicação com o Canvas (identificadores para uso com [send] e [receive]), a etiqueta (rótulo de identificação) e as cores (do plano de fundo e da etiqueta).
Cada vez que um objeto Canvas recebe, através de seu receive, uma mensagem com o conteúdo "get_pos", ele envia através de seu send o par de inteiros correspondentes à posição (medida em pixels) do canto superior esquerdo do Canvas dentro do patch. O mesmo objeto pode receber uma mensagem "pos X Y" para reposicionar o Canvas dentro do patch. Estas mensagens podem ser enviadas/recebidas através dos mecanismos usuais, ou seja, com os objetos [send nome_do_receive_do_canvas] e [receive nome_do_send_do_canvas], sendo que o envio pode ser feito também através de mensagens ";<ENTER>nome_do_receive_do_canvas get_pos" ou ";<ENTER>nome_do_receive_do_canvas pos X Y".
Para permitir uma melhor visualização do espaço onde os pólos e zeros podem ser posicionados, crie um Canvas grande para representar o quadrado complexo [-1,1]×[-1,1], além de quatro Canvas pequenos para representar os pólos e zeros. O Canvas que representa o fundo pode fornecer uma referência para as coordenadas dos demais elementos: se (X0,Y0) é a posição do fundo, então cada posição (X,Y) recebida dos pólos e zeros pode ser ajustada como (X-X0,Y-Y0) antes das demais conversões.
Pólos e Zeros
Cada pólo ou zero será representado por um Canvas diferente. Para garantir que o filtro produz coeficientes reais, pólos e zeros não podem ser independentes: vimos que 2 pólos complexos (não reais) precisam ser conjugados, e o mesmo vale para 2 zeros complexos (não reais). Para simplificar nosso trabalho, forçaremos o polo P2 a ser sempre espelhado em relação a P1; ou seja, se P1=a+ib então P2 será P2=a-ib. Se P1 for reposicionado, P2 terá que ser reposicionado automaticamente para preservar o espelhamento, e vice-versa (se P2 for reposicionado, P1 será reposicionado também). A mesma restrição de espelhamento valerá para os zeros Z1 e Z2. Esta restrição acarreta perda de generalidade, pois não poderemos representar dois zeros reais diferentes, ou dois pólos reais diferentes (paciência!).
Outra restrição importante é a da estabilidade do filtro resultante, que deve ser imposta através das condições |P1|<1 e |P2|<1 (na realidade basta uma delas, já que |P1|=|P2|). Zeros não precisam seguir esta restrição. Sempre que um dos pólos tentar ser reposicionado fora do círculo unitário, você deve forçar o reposicionamento deste elemento no interior do círculo usando a mensagem "pos X Y". Considere que se P=a+ib com |P|≥1, então você deve computar as coordenadas (X,Y) em pixels do ponto normalizado P/|P|, usando truncamento, e recuar 1 pixel "para dentro" do círculo, redefinindo X=X-1*sign(a) e Y=Y-1*sign(b), onde sign(x)=x/|x| se x!=0 e sign(0)=0.
A conversão das posições dos elementos gráficos para os coeficientes do filtro envolve três etapas: a conversão linear dos valores em pixels para valores no intervalo [-1,+1], a conversão da representação Cartesiana para Polar, e o computo dos coeficientes do filtro a partir da função de transferência fatorada. Se preferir, você pode refazer as fórmulas dos coeficientes considerando Pólos e Zeros na forma Cartesiana a+ib, e abrir as expressões fatoradas (1-(a+ib)z-1)(1-(a-ib)z-1); lembre-se que o termo a0 aparece multiplicado também pelos coeficientes a1 e a2.
Observações adicionais
(0) Uma sugestão didática para a ordem de implementação é: criação dos canvas, testes para obtenção e redefinição das coordenadas dos canvas, implementação das fórmulas de conversão, testes, espelhamento de pólos e zeros, testes, restrição dos pólos dentro do círculo, testes.
(1) Para que os pólos e zeros não sejam desenhados atrás do fundo, crie o Canvas do fundo antes dos pólos e zeros (a ordem de criação define implicitamente a "profundidade").
(2) Cuidado para não escrever acidentalmente um loop infinito, definindo P2 a partir de P1 e P1 a partir de P2. Apenas os elementos que tiveram sua posição modificada devem propagar a posição espelhada para o elemento conjugado.
(3) Use apenas P1 e Z1 no cômputo dos coeficientes do filtro, assim você evitará possíveis erros numéricos nas conversões de/para pixels, que pudessem desestabilizar a propriedade da conjugação complexa entre P1 e P2 ou Z1 e Z2.
Bom Trabalho!