Brain Dump

terça-feira, 3 de março de 2009

O olhar do macaco

RicBit e um povo da Engenharia vão até o prédio da psicologia se encontrar com um biólogo europeu. Por trás dessa aglomeração interdisciplinar, uma tese sobre a origem da humanidade, baseada na análise do olhar dos macacos. E tudo isso culminando com a conclusão do dilema: afinal, magenta é ou não uma cor? Suspense, emoção e integrais os aguardam no resto desse post!


No post anterior eu expliquei o que era o magenta, então agora é hora de ver o que é uma cor. O Kandel diz que cor é "uma experiência subjetiva relacionada à composição espectral da luz que atinge o olho", mas isso é uma simplificação. Pra ter uma experiência cromática você não precisa de luz, e nem de olho.

Certamente você já ouviu que as pessoas que levam porrada ficam "vendo estrelas". Não é só uma expressão, isso acontece de verdade. O que ocorre é que os mesmos cones da retina que convertem luz em sinais elétricos também são sensíveis a pressão. Então, se você apertar o sensor (por exemplo, com uma paulada bem dada), você vai gerar o mesmo impulso elétrico que teria sido gerado se você estivesse olhando para uma fonte luminosa. Daí, a sensação é de ver estrelas, mesmo que você esteja sem nenhuma luz, no completo escuro.

Certo, então podemos usar os olhos mesmo sem luz. Mas nem os olhos são realmente necessários. Os sinais elétricos que vão dos cones até o córtex visual primário podem ser injetados diretamente com eletrodos: nesse caso você tem a experiência da cor sem precisar da retina. Ou então você pode injetar uma droga psicoativa como o LSD, que altera o equilíbrio químico do cérebro, causando uma experência cromática mais intensa que colocar uma camiseta branca e um monte de roupas coloridas dentro da máquina de lavar.

Mas esses são efeitos colaterais. Na maior parte do tempo, você vai mesmo enxergar cor como sendo uma conseqüência do espectro eletromagnético. Curiosamente, tudo que eu posso afirmar é que a cor é uma conseqüência: eu não posso dizer, por exemplo, que a cor é uma função do espectro, porque ela falha em satisfazer a definição de função: você pode ter duas cores para um mesmo espectro, e dois espectros para a mesma cor. O primeiro caso pode ser visto facilmente na imagem abaixo:

Os dois MSX produzem a sensação de duas cores diferentes, mas na verdade são o mesmo espectro (pode conferir no Photoshop)! A explicaçao é que o cérebro tem uma etapa diferencial após a captura do espectro pelos cones, então um espectro isolado pode produzir duas percepções diferentes, dependendo do que está em volta.

O segundo caso é mais chatinho. Eu poderia mostrar aqui dois espectros diferentes, falar que eles geram a mesma cor, e pedir pra vocês acreditarem em mim; mas resolvi fazer melhor que isso: criei um editor de espectro online! Para usar, basta desenhar um espectro com o mouse em cima da caixa bege, logo abaixo vai aparecer qual a cor que o seu olho enxergaria se estivesse vendo esse espectro. Veja como existem um monte de maneiras diferentes de fazer o amarelo. Você consegue fazer o magenta?



Source do editor de espectros online

Eu escrevi o editor usando GWT, a abscissa é o comprimento de onda (de 430nm até 650nm), a ordenada é a intensidade luminosa (em escala log), e as funções de transferência eu peguei no site do Colour and Vision Research Lab da Universidade da Califórnia em San Diego. Se você estiver lendo esse post por RSS, pode ser que o seu leitor faça sanitização de javascript e o applet não apareça; nesse caso é só ver o post original no www.ricbit.com.

Ok, agora nós sabemos que uma cor pode ser gerada por vários espectros diferentes. Por que isso acontece? A resposta é que o nosso sistema visual joga fora a maior parte da informação que entra, e para explicar os detalhes temos que entender como funcionam os cones.

Um cone é uma coisinha bem burrinha. A saída dele é binária: manda impulso elétrico ou não manda impulso elétrico; e o disparo é totalmente aleatório. Mas apesar ser aleatório, não é ruído puro, porque a probabilidade de disparo pode ser controlada através da luz que entra no sensor. Algumas freqüências fazem ele disparar com mais facilidade, e se você aumentar a intensidade da luz ele também aumenta a probabilidade.

No fim do processo, o cérebro vai interpretar esse trem de pulsos e gerar um único valor, que é uma espécie de média ponderada da luz que atingiu o cone. De outra maneira, é como se o cérebro calculasse o produto interno da função de transferência do cone com o espectro da luz incidente (lembrando que produto interno de distribuições é definido como a integral do produto das distribuições; e, de fato, no source do meu editor de espectro tem um método que faz exatamente isso). Em resumo, um cone gera um valor escalar a partir da entrada.

Mas com um cone sozinho nós não conseguiríamos ver cores. Humanos normais possuem três deles, e na verdade é aí que entra a história do macaco. O tal biólogo europeu tinha notado que o olhar dos macacos americanos era diferente do olhar dos macacos africanos: os primeiros tinham só dois cones, enquanto que os segundos tinham três cones. Isso serve de evidência de que a espécie humana surgiu na África, e não na América (já que os macacos de lá tem o mesmo número de cones que a gente).

A quantidade de cones define quão rica é sua experiência cromática. Alguns humanos nascem com apenas dois cones: são os daltônicos, que enxergam menos cores que os humanos normais. Segundo a wikipedia, entre as mulheres existe um grau significativo de tetracromia também, o que faz algum sentido pra mim (isso explicaria aquelas mulheres chatas que falam: "você não limpou direito, olha uma mancha aqui", e você olha, olha e não vê mancha alguma; vai ver ela era tetracromática e a mancha era de uma cor que você não enxerga).

A resposta dos três cones de um humano normal é a abaixo, nós os chamamos S, M, L (baseado nos comprimentos de onda que eles cobrem, small, medium e large).

Vamos juntar tudo agora. Um cone retorna um valor escalar, e nós temos três deles. Se podemos formar uma tripla ordenada de escalares com a saída dos cones, então na verdade eles definem um espaço vetorial de dimensão três. Uma cor, então, é simplesmente um vetor desse espaço.

Ora, todo espaço vetorial admite uma base. Como o espaço tem dimensão três, você pode escolher três vetores quaisquer pra formar essa base, desde que sejam linearmente independentes. O mais comum é escolher o vermelho, o verde e o azul, porque essas são as cores que você percebe quando estimulamos os nossos cones de maneira independente. Isso dá origem ao sistema RGB que todo mundo conhece.

Você pode aplicar uma transformação linear qualquer nessa base pra gerar outras, como por exemplo, o YIQ usado em transmissão NTSC, ou o YJK usado nos chips de vídeo do MSX2+. Alguns sistemas de cores são não-lineares, como o YCbCr da compressão MPEG. Um sistema não-linear muito usado por artistas é o HSV, que deforma o cubo RGB em um cilindro, onde o raio é a saturação, a altura é o brilho, e o ângulo é o tom da cor.

O sistema HSV pode ser realizado fisicamente se você tiver disponível um laser de freqüência variável e uma lâmpada de cor branca. Se você mudar a freqüência do laser, muda o tom da cor; se mudar a intensidade da lâmpada, mantendo a intensidade do laser fixa, você muda a saturação; e se mudar a intensidade dos dois ao mesmo tempo, muda o brilho. Por exemplo, eu poderia gerar um tom de rosa com um laser vermelho e com branco não muito forte. Um screenshot do editor de espectro gerando esse rosa é assim:

E agora chegamos ao ponto crucial do problema: como você gera o magenta com esse método? A resposta é: você não gera! Não dá pra fazer o magenta assim, porque um único laser não consegue estimular o cone do vermelho e o cone do azul sem estimular junto o cone do verde.

Isso é evidência de que o magenta não é uma cor? Claro que não, é só uma evidência de que esse método específico de gerar cores não é capaz de percorrer todo o espaço vetorial, e algumas cores ficam de fora.

A moral da história é que pra trabalhar com cognição você precisa ter um time interdisciplinar. Se o seu time não tiver alguém de exatas pra interpretar a álgebra linear do seu problema, você corre o risco de escrever bobagens como "magenta não é uma cor".

No próximo post, a conclusão da série do magenta, com as batalhas que a coitada da cor passou!

Marcadores: , , , , , ,

sábado, 25 de outubro de 2008

Dez anos de MUST

É díficil achar alguém na faixa dos 30 anos que trabalhe com computadores e não conheça o MSX, um computador pessoal de 8 bits que fez um sucesso enorme por aqui. No seu auge, entre 85 e 91, estima-se que mais de 400 mil máquinas tenham sido vendidas no Brasil.

Mas embora esse período tenha sido o auge em número de usuários, não foi o ápice na parte técnica. Os mais avançados softwares brasileiros de MSX foram feitos no período de 1996 a 2004, e há dois motivos pra isso. O primeiro é que esse foi o início da internet comercial, então muitos dos manuais e datasheets que antes eram raros, passaram a ser difundidos livremente pelas listas de discussão. O segundo é que essa também foi a época em que começaram a se formar os primeiros engenheiros que cresceram com o MSX na infância, e toda essa criançada estava sedenta pra usar os novos conhecimentos na sua velha maquininha.

Eu mesmo fiz um monte de brincadeiras nessa época, talvez a mais conhecida tenha sido o meu emulador BrMSX. Mas agora, em 2008, um outro software que fiz completa dez anos, e nada mais apropriado que comemorar com um detalhado making of. Apresento a vocês o Music Station, ou como também era carinhosamente conhecido, o MUST:


Vamos voltar uma década no tempo: em 1998, a internet crescia, nasciam os primeiros grandes portais nacionais, e começava a surgir uma prática que hoje em dia é extremamente comum: o compartilhamento de músicas em mp3. Primeiro por FTP e IRC, depois pelo Napster e Audiogalaxy, em pouco tempo os arquivos mp3 estavam por toda a parte.

Naturalmente, quem ainda brincava com o MSX também queria entrar na onda. Mais de uma vez me perguntaram "Ricardo, você não pode fazer um programinha pra tocar mp3 no MSX?". Qualquer criatura com um mínimo de bom senso iria perguntar "pra quê?". Mas bom senso nunca foi o meu forte :)

Eu já sabia de antemão que mp3 no MSX não era viável, o coitado do Z80 não iria dar conta. Mas a essa altura eu já sabia evitar um erro muito comum em desenvolvedores, o de prestar atenção no que o usuário pede, ao invés de focar no usuário quer. Sure, eles estavam pedindo mp3, mas o que eles queriam mesmo era só tocar música no MSX. Se fosse um outro formato qualquer não faria diferença. Como eu tinha acabado de cursar Processamento Digital de Sinais na Poli (com o saudoso prof. Max Gerken), achei que poderia encarar a construção de uma solução customizada pro MSX.

Hora de calibrar os objetivos: eu decidi fazer um player que tivesse a melhor qualidade possível pro MSX, que rodasse em qualquer modelo de MSX, incluindo os nacionais, e que conseguisse espremer pelo menos um minuto de música em uma MegaRAM de 256kb, o modelo mais comum. O primeiro passo, portanto, é ver se o MSX dá conta de tocar um sample com qualidade aceitável.

O MSX usa como gerador de som o chip AY-3-8910 (popularmente PSG), que é basicamente um gerador de ondas quadradas. Pra tocar um Lá central, você especifica a freqüência de 440Hz, acerta o volume apropriado, e se diverte com o barulhinho que ele produz. Se você for um mago do chiptune, dá pra fazer músicas bem legais com o PSG, mas definitivamente ele não foi feito pra reprodução de áudio digital. Para conseguir isso, nós temos entender como o chip funciona por dentro.

Internamente, o gerador de ondas quadradas é só um contador digital, que alterna o estado de um flip-flop depois do término de meio período. O chip tem três geradores que você pode ligar ou desligar de maneira independente através de um mixer. Quem tem experiência com chips TTL sabe que sinais desligados usualmente não são interpretados como nível lógico zero, mas sim como nível lógico um.

Pro nosso ouvido tanto faz, um sinal constante é silêncio sempre, independente dele estar em zero volts ou em cinco volts, mas o segredo da coisa é que o controle de volume é uma etapa analógica feita depois do mixer. Por isso, se você desligar o gerador através do mixer, e mudar bem rapidamente o controle de volume, você pode improvisar qualquer forma de onda que quiser! Como o PSG tem 16 volumes possíveis, isso significa que o MSX pode emular um PCM de 4 bits, mesmo sem ter sido projetado pra isso.

Quando o sinal está em nível lógico um, basta variar o volume!

Agora basta ligar esse PCM num timer de alta resolução e pronto. Quer dizer, exceto pelo fato do MSX não ter um timer de alta resolução. O melhor que ele tem é um timer que dispara no início do retraço vertical, a 60Hz, que é uma freqüência baixa demais pra gente. Nesse caso, o jeito é apelar pra gambiarra criatividade :)

Como o Z80 do MSX não tem cache, nem prefetching, nem nenhuma dessas firulas de processadores modernos, os opcodes sempre levam o mesmo tempo pra rodar. Assim, você pode criar um pseudo-timer simplesmente contando quantos ciclos de clock seu programa leva pra executar. Se nós precisássemos de uma rotina que lesse um byte da memória, mandasse pro PSG, e levasse exatamente 50 clocks pra rodar, uma possibilidade seria:

LD A,(HL) ; 8 clocks
LD A,(HL) ; 8 clocks
INC HL ; 7 clocks
OUT (0A1h),A ; 12 clocks
NOP ; 5 clocks
NOP ; 5 clocks
NOP ; 5 clocks

Pra fazer um timer assim, além de programar em assembly, você ainda precisa resolver um subset sum dos opcodes, o que é extremamente divertido (eu assumo que se você está lendo até aqui, deve achar essas coisas divertidas também :). Note como é importante fazer o padding correto, além de colocar 3 NOPs, eu também repliquei o carregemento da memória, só pra fazer a conta bater certinho nos 50 clocks.

Agora que temos um jeito de reproduzir o som, precisamos escolher a sampling rate. Pra conseguir o objetivo de 1 minuto em 256kb, o áudio certamente terá que ser comprimido. Então a sampling rate precisa ser baixa o suficiente pra poder suportar a descompressão em real time entre cada amostra, e alta o suficiente pra não perder muita qualidade de som. Eu escolhi 11kHz; como o clock do MSX é 3.57MHz, isso dá um total de 324 clocks por amostra. Não é muito, mas alguma compressão dá pra fazer.

Vamos calcular a compressão agora. Pra colocar 60 segundos a 11kHz em 256kb, nós precisamos de, no máximo, 256k*8/60/11k = 3.1 bits por amostra. Nosso sinal original tem 4 bits por amostra, então realmente a compressão é necessária. O Z80 não tem fôlego pra implementar transformadas, então não podemos usar nada de DCT, e nem mesmo Haar.

O jeito é modelar alguma coisa bem simples mesmo, tipo um código de tamanho variável como o código de Huffman. Esse tipo de código funciona tanto melhor quanto menos uniforme for o seu histograma. Pegando uma música típica como teste, chegamos em uma entropia de 2.6 bits; mas, por outro lado, a primeira diferença tem entropia ainda menor, apenas 2 bits!


Vamos codificar a diferença então. Com apenas 324 clocks não dá pra implementar todo o Huffman, mas tem uma maneira de simplificar, que é usando compressão com perdas. Como 99.3% das diferenças estão na faixa de -3 a 3, eu posso saturar nesses valores pra deixar o código mais simples. O código que utilizei foi o abaixo:

3 1111
2 1110
1 01
0 00
-1 10
-2 1101
-3 1100

No final a implementação desse código ficou menor do que eu esperava, apenas 169 clocks. Isso significava que eu tinha 198 clocks sobrando, com a cpu parada. Eu poderia ter aprimorado o codec com uma compressão mais eficiente, mas pensei que seria mais divertido, ao invés disso, usar esses clocks sobrando pra adicionar animações. Nesses clocks sobrando eu acabei colocando um osciloscópio com a forma de onda tocada, um banner animado na parte inferior, e no cantinho ainda sobrou cpu pra colocar um pingüim dançando!

Nesse ponto tudo que faltava era uma tela de abertura. Como no final o programa acabou ficando uma experiência multimídia, eu resolvi que ele seria uma homenagem à série Disc Station da Compile, que primava por seus excelentes demos. Eu chamei então o meu programinha de Music Station, e como os Disc Station originais sempre começavam com uma menina bonitinha, eu pedi ao meu irmão que desenhasse uma pingüinha no mesmo estilo. O desenho original que ele fez foi o abaixo:

Adaptar desenhos no MSX é tão complicado quanto adaptar código, a resolução é de apenas 256x192 e ainda tem o problema do color clash (cada bloco de 8x1 pixels pode ter no máximo duas cores). O pingüim tomando sol no canto da imagem teve que sumir, eu não tinha resolução pra isso. Já na pingüinha, eu tive que usar traços bem grossos no contorno, pra minimizar o clash, mas mesmo assim ainda sobraram algumas partes borradas. A solução foi usar sprites pra cobrir esse borramento.

Consegue achar as diferenças?

Por fim, bastou criar um logo, inspirado no logo original do Disc Station, e o MUST estava finalmente pronto! Pra quem quiser brincar com ele, abaixo tem uma versão em .dsk que pode ser carregada em emuladores, como o blueMSX ou o openMSX, e também o código fonte original.

Imagem de disco para rodar em emuladores
Código fonte original do Music Station, em assembly Z80

Na época, a comunidade MSX adorou o MUST. Como eu esperava, o pessoal começou a compartilhar músicas convertidas, do mesmo jeito que o povo do PC compartilhava mp3; e como eu publiquei a engine do MUST em GPL, um monte de gente reaproveitou o código, sendo que a engine foi usada até em joguinhos. Eu até animei a fazer uma versão com vídeo também, mas isso já é outra história, pra outra ocasião :)

Parabéns pelos dez anos, Music Station!

Agradecimentos à Ila, ao Acidx e ao Sturaro por ajudar na arqueologia digital :)

Marcadores: , , , , , ,