Um dos motivos que me fizeram ficar distante do blog por uns tempos foi o meu mestrado, cujo semestre acabou essa semana. E como estou trabahando direto com Java atualmente, tive que usar a imaginação para continuar praticando meus conhecimentos em Ruby. Para quem não sabe, estou fazendo mestrado em Sistemas Interativos de Visualização, com muitas cadeiras ligadas a Computação Gráfica, Animação e Visualização de Dados. Em muito dos trabalhos, utilizei uma das minha ferramentas favoritas, o Processing, mais precisamente sua alternativa rubista: o ruby-processing.

O Poder do JRuby

Uma das melhores vantagens fornecidas pelo JRuby é a possibilidade de utilizarmos as trocentas ferramentas já existentes no mundo Java utilizando Ruby. E foi exatamente isso que Jeremy Ashkenas fez. Perceba que a integração é relativamente simples entre as duas linguagens:

require 'java'

module Processing

  # Conditionally load core.jar
  require "#{RP5_ROOT}/lib/core/core.jar" unless Processing.online? || Processing.embedded?
  import "processing.core"
  .
  .
  .
class App < PApplet
  .
  .


Ao importar o .jar do Processing , temos à disposição todas as classes, inclusive a PApplet que é a que nos disponibiliza as principais funções de desenho. O resto do código é praticamente formado apenas por wrappers e funções auxiliares de integração. A lógica toda já está pronta, e em Java.

Aconselho fortemente uma olhada no código do ruby-processing para quem quiser aprender como se faz uma aplicação completa usando JRuby

Os Corais de Bach

Em uma disciplina de Visualização de Dados, um trabalho consistia em escolher um dataset do site Machine Learning Repository e criar uma maneira de visualizar esses dados. Escolhi os Corais de Bach, devido ao fato de eu ser fã do magnífico compositor alemão.

O primeiro desafio foi destrinchar a base de dados. Para vocês terem uma idéia, aqui segue um exemplo de uma peça de coral contida na coleção:

(1 ((st 8 ) (pitch 67) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 12) (pitch 67) (dur 8 ) (keysig 1) (timesig 12) (fermata 0))((st 20) (pitch 74) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 24) (pitch 71) (dur 6) (keysig 1) (timesig 12) (fermata 0))((st 30) (pitch 69) (dur 2) (keysig 1) (timesig 12) (fermata 0))((st 32) (pitch 67) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 36) (pitch 67) (dur 6) (keysig 1) (timesig 12) (fermata 0))((st 42) (pitch 69) (dur 2) (keysig 1) (timesig 12) (fermata 0))((st 44) (pitch 71) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 48 ) (pitch 69) (dur 8 ) (keysig 1) (timesig 12) (fermata 1))((st 56) (pitch 71) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 60) (pitch 74) (dur 8 ) (keysig 1) (timesig 12) (fermata 0))((st 68 ) (pitch 72) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 72) (pitch 71) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 76) (pitch 69) (dur 8 ) (keysig 1) (timesig 12) (fermata 0))((st 84) (pitch 67) (dur 8 ) (keysig 1) (timesig 12) (fermata 1))((st 92) (pitch 71) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 96) (pitch 71) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 100) (pitch 72) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 104) (pitch 74) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 108 ) (pitch 74) (dur 6) (keysig 1) (timesig 12) (fermata 0))((st 114) (pitch 72) (dur 2) (keysig 1) (timesig 12) (fermata 0))((st 116) (pitch 71) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 120) (pitch 69) (dur 8 ) (keysig 1) (timesig 12) (fermata 1))((st 128) (pitch 67) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 132) (pitch 71) (dur 8 ) (keysig 1) (timesig 12) (fermata 0))((st 140) (pitch 72) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 144) (pitch 74) (dur 8 ) (keysig 1) (timesig 12) (fermata 0))((st 152) (pitch 72) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 156) (pitch 71) (dur 12) (keysig 1) (timesig 12) (fermata 0))((st 168) (pitch 67) (dur 8 ) (keysig 1) (timesig 12) (fermata 1))((st 176) (pitch 71) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 180) (pitch 74) (dur 8 ) (keysig 1) (timesig 12) (fermata 0))((st 188 ) (pitch 72) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 192) (pitch 71) (dur 8 ) (keysig 1) (timesig 12) (fermata 0))((st 200) (pitch 69) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 204) (pitch 67) (dur 6) (keysig 1) (timesig 12) (fermata 0))((st 210) (pitch 69) (dur 2) (keysig 1) (timesig 12) (fermata 0))((st 212) (pitch 71) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 216) (pitch 69) (dur 8 ) (keysig 1) (timesig 12) (fermata 1))((st 224) (pitch 71) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 228 ) (pitch 74) (dur 8 ) (keysig 1) (timesig 12) (fermata 0))((st 236) (pitch 72) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 240) (pitch 71) (dur 4) (keysig 1) (timesig 12) (fermata 0))((st 244) (pitch 69) (dur 8 ) (keysig 1) (timesig 12) (fermata 0))((st 252) (pitch 67) (dur 8 ) (keysig 1) (timesig 12) (fermata 1)))

Pra quem conhece Lisp, essa bizarrice não assusta tanto, mas eu precisava de uma maneira de tornar essa maçaroca em algo entendível. Aí vem a beleza do Ruby e sua facilidade para processamento de texto. Com poucas linhas, um arquivo com 100 peças de corais como a mostrada acima vira uma estrutura simples com tudo que eu preciso para minha visualização:

lines = []
  File.open("bach.lisp", "r") do |infile|
       while (line = infile.gets)
           lines << "#{line}" unless line.size == 1 #ignora linhas em branco
       end
  end

Com tudo mastigado, basta partirmos para as operações matemáticas.

Hora do Desenho

Na minha busca por reconhecer padrões nas músicas em minhas visualizações, escolhi arcos, porque eles passam uma noção de duração facilmente perceptível, e as cores foram escolhidas para as notas, ficando o eixo X para o momento em que as notas iniciam.

def draw
  background 0,0,0
  draw_labels
  draw_rectangles
  @arcs.each do |ar|
    noFill
    stroke(ar.c)
    smooth
    semicircle(ar.st+ar.duration, X_AXIS, ar.st,X_AXIS )
    line(ar.st+ar.duration, X_AXIS+10, ar.st+ar.duration, X_AXIS+10+(ar.pitch*2))
  end

end

def semicircle xLeft, yLeft, xRight, yRight
  pushMatrix
    diameter = dist xLeft, yLeft, xRight, yRight
    angle = atan2(yRight - yLeft, xRight - xLeft)
    arc((xLeft + xRight)/2, (yLeft + yRight) / 2, diameter, diameter, angle, angle + PI)
  popMatrix
end

O resultado, pode ser conferido nos screenshots abaixo:

chorales2

chorales

Se levarmos em conta o tempo curto entre os trabalho, o resultado alcançado foi interessante. Meu código possui muitas duplicações e o algoritmo não está nada otimizado. Com mais tempo, faço uma versão melhor, aonde qualquer arquivo de música MIDI possa ser interpretado.