Skip navigation

 

a terceira sessão vai aprofundar as máquinas de desenhar e máquinas jogáveis. vamos criar máquinas de desenhar à base de agentes autónomos que divagam sobre bitmaps ou imagens, e máquinas jogáveis à base de som capturado pelo computador. já são programas bem interessantes que vos podem dar pistas e código base para a realização dos projectos. 

 

é também a primeira sessão em que vemos um input contínuo e dinâmico aplicado a formas que desenham: o som. vamos ver a importância de o suavizar com um filtro lowpass para evitar a rispidez brusca dos resultados directos. 

 

nas sessões seguintes vamos abrandar um pouco o ritmo, rever noções importantes de síntese de imagem que temos vindo a analisar, rever os comandos essenciais do processing que permitem o controlo de fluxo do nosso programa e de que maneira tornamos inputs dinâmicos que chegam ao computador interactivos. fazer exercícios, exercícios. já percorremos um caminho muito importante, agora é sedimentar os conceitos pela prática através de exercícios.

 

 

1. máquinas de desenhar, parte 2

antes de começarmos com o tema principal de hoje, analisámos dois sketches que continuam as noções circulares e trigonométricas da sessão anterior. são eles o p5_circulos e p5_circulos_bezier. este primeiro sketch, p5_circulos, demonstra como podemos abstraccionar e criar um object/class a partir do sketch da sessão passada que desenhava apenas um círculo centrado no ecran. criamos então o objecto Circ  que mantém as variáveis essenciais para desenhar um círculo com o centro em qualquer sítio do ecran, desde que lá cliquemos, lançamos um novo circulo a desenhar. tornando os circulos uma classe, vamos então explorar um pouco diferentes composições gráficas com diferentes parâmetros aplicados aos círculos.

circulos-000368

a esta class Circ pertencem as seguintes variáveis, que vão ser úteis na criação dos nossos programas que desenham círculos arbitrariamente no ecran:

 

///// class circulos

 

class Circ {

 

  float raio; // o raio actual polar do circulo

  float angulo; // o ângulo actual polar do circulo

 

  float ppx, ppy; // o x e y anteriores cartesianos

  float px,py; // o x e y actuais cartesianos

 

  float cx, cy; // o x e y cartesianos que definem o centro

 

  float curv; // a quantidade de curvatura do círculo

  float raio_ini, raio_fim, raio_inc; // o raio inicial, final, e incremento por frame

  boolean active; // se a função draw da classe está activa ou não

 

 

quando criamos um novo círculo, este vai iniciar-se com o raio inicial a zero, e define valores aleatórios para o raio final, o incremento do raio por frame, a curvatura, ie, quanto de magnitude vai cada angulo do circulo incrementar. este comportamento define-se no construtor desta class Circ que estamos a criar:

 

  Circ(){

    raio_ini = 0;//random (0,230); 

    raio_fim = random(10,100); //random (10,370);

    raio_inc = random (0.1,1.2);    

    raio = raio_ini;

    // se o raio final é menor, o raio_inc tem de ser negativo

    if(raio_fim < raio_ini)

      raio_inc = -raio_inc;

    active = false;//true;

 

    curv = (random(1)<0.5) ? random (0.1,0.3) :  random (0.3,1.73)  ;

    // definir a direcção aleatória do circulo

    if (random(1)<0.5)  

        curv *= -1 ;

 

    //centros

    cx = width/2 + random(-width/3,width/3);

    cy = height/2 + random(-height/3,height/3);

 

    angulo = random(TWO_PI); //iniciar o angulo do circulo noutras pos

 

  }

 

 

depois, cada frame que passa, chamamos a função draw da class Circ. esta função apenas é executada quando a booleana active estiver a true, facto que é falso quando chamamos apenas o construtor, o que implica que se quisermos que o círculo desenhe, logo a seguir ao construtor temos de colocar uma linha onde activamos a variável active. isso faz-se com uma atribuição simples:

 

  circulos[i] = new Circ(); // construir o objecto

  circulos[i].centro(mouseX,mouseY); // colocar as vars centro com as coords rato

  circulos[i].active = true; // activar o circulo para começar  a desenhar

 

 

a função draw da class Circ vai então manter o estado das variáveis polares raio e ângulo, incrementando-as de acordo com os parâmetros curv e raio_inc, e depois converte estes valores polares para valores cartesianos.

 

  void draw(){

    if( active ) {

 

     // armazenar os valores cartesianos anteriores

      ppx = px;

      ppy = py;

 

     // calcular os novos valores polares

      angulo += curv;  // acumular na variável ângulo incrementos de curv

      raio += raio_inc; // acumular na variável raio incrementos de raio_inc

 

      //já chegou ao fim se o raio actual for maior que o raio_fim

      if(raio_inc > 0){

        if(raio>raio_fim)

          active=false;  // a boolean active é desligada, logo o Circ deixa de desenhar

      } else {

        if(raio<raio_fim)

          active=false;      

      }     

 

     // calcular as coordenadas cartesianas a partir das polares

      px = cos(angulo)*raio + cx;

      py = sin(angulo)*raio + cy;

 

     // desenhar uma linha com preto quase-transparente      

      stroke(0,50);

      line(px,py,ppx,ppy);

 

    } // end if active

 

  } //end draw

 

 

assim, já conseguimos criar objectos da classe Circ, em vez de ter apenas um círculo a desenhar no ecran, já conseguimos colocar vários.

circulos-003007

para tal, como temos vindo a analisar ao longo destas três sessões, criamos uma array de objectos Circ através da seguinte declaração:

 

Circ  circulos[]; // uma array de circulos do tipo Circ

 

inicializamos essa array através da função init_circulos(), chamada na função setup.

 

void init_circulos() {

 

  circulos = new Circ[10];

 

  for(int i = 0; i < circulos.length; i++) {

    circulos[i] = new Circ();

  } 

 

}

 

e a função draw principal do nosso sketch de processing chama continuamente duas funções, uma que desenha os circulos, outra que cria os circulos apenas se estiveremos a clickar no rato.

 

///// draw circulos

 

void draw_circulos() {

  for(int i = 0; i < circulos.length; i++) {

//    strokeWeight(i+0.1);

    circulos[i].draw();

  } 

}

 

 

 

void new_circ(){

 

  if(pmouseX!=mouseX && pmouseY!=mouseY) // só se o rato antigo não é igual ao actual…

  for(int i = 0; i < circulos.length; i++) {

      if(!circulos[i].active){

          circulos[i] = new Circ();

          circulos[i].centro(mouseX,mouseY);

          circulos[i].active = true; //aqui é q se inicia  

          break; // importantíssimo, senão 10 circs      

      }

  }

 

}

 

 

esta última função só é chamada se o rato estiver premido, assim

 

  if(mousePressed)

    new_circ();

 

e só corremos o ciclo for da função se as coordenadas anteriores do rato forem distintas das actuais. o ciclo for vai descobrir o primeiro objecto inactivo da nossa array de objectos, logo que encontra um inactivo cria um circulo novo nessa posição da array com as novas coordenadas do rato x e y. por isso é que temos a primeira instrução dentro do ciclo for if(!circulos[i].active), e todo o bloco de código subsequente apenas é executado se a active desse Circ estiver a false.

 

 

assim, já conseguimos criar círculos polifónicos, onde vários elementos escrevem simultaneamente na mesma frame para a tela digital.

 

o sketch que vimos a seguir é um acrescento a esta mecânica. acrescentámos uma linha bezier, que é um comando gráfico mais complicado, pois recebe vários argumentos, e acrescentámos essa linha à medida que vamos clickando com o rato nas várias posições, vamos alterando os pontos de controlo e pontos onde a linha escreve. curvas bezier são curvas paramétricas, geradas a partir de 4 pontos. ao definirmos 4 pontos, as curvas bezier são expressões matemáticas que preenchem uma linha curva que passa pelo 1 e últimos pontos destes 4 pontos, e onde os dois pontos do meio são pontos de controlo, que permitem afinar/distorcer a curvatura. aqui têm mais informações sobre curvas bézier, http://en.wikipedia.org/wiki/Bézier_curve, e se já usaram programas vectoriais, como o inkscape ou o illustrator, sabem do que estou a falar. o processing facilita bastante a inserção de curvas bezier, pois já tem uma função que se chama bezier, e que recebe 4 pontos x 2 vars por ponto = 8 argumentos (ou 4 x 3 = 12 args no caso 3d).

 

em vez de criamos objectos para manipular esta curva, vamos apenas criar as variáveis necessárias, e a partir dessas variáveis, vamos inserindo sempre as coordenadas actuais do rato clickado, num comportamento de pilha em fifo (first-in-first-out, http://en.wikipedia.org/wiki/FIFO)

 

as variáveis adicionadas ao programa dos círculos são:

 

float x1,x2,x3,x4,y1,y2,y3,y4; //os pontos da linha bezier

 

 

o comportamento fifo é obtido através do seguinte código:

 

void draw_bezier(){

 //1. incrementar os pontos

  x4 = x3;

  y4 = y3;

  x3 = x2;

  y3 = y2;

  x2 = x1;

  y2 = y1;

  x1 = mouseX;

  y1 = mouseY;

 

  //2. desenhar a curva nos pontos

  bezier(x1,y1,x2,y2,x3,y3,x4,y4);

 

}

 

neste código, as coordenadas mouseX e mouseY escrevem em último lugar para as variáveis do primeiro ponto, e todos os outros pontos se copiam em cascata, o que faz com que mantenhamos um estado onde o ponto actual do rato estará na variável x2 e y2 na frame seguinte, na outra frame estará no ponto 3 e finalmente, estará no ponto 4; depois perde-se, pois os novos pontos já entraram na pilha.

 

o resultado é que além dos círculos criados nas posições clickadas do rato, temos agora também uma linha bezier que acompanha os cliques nessas posições.

circulosbezier-0004781

circulosbezier-000623

 

 

chegamos então ao nosso tema principal de hoje, onde vamos criar agentes autónomos que fazem linhas pela tela de acordo com informações de pixeis armazenadas em imagens, ou bitmaps.

 

antes de proseguirmos com o nosso objectivo, analisámos como se lêem imagens do disco e se armazenam as imagens em variáveis, e finalmente, como podemos aceder à informação de cor de cada pixel. antes de mais analisamos a cor, e o tipo de dados color.

 

o processing tem nativamente um tipo de dados color que armazena a informação rgb de um pixel. este tipo de dados color pode ser criado com a função color. o mesmo nome,  mas são coisas distintas. um corresponde a um tipo de dados, outro a uma função, depende da forma como os usamos.

 

posso então definir cores assim:

 

color c1 = color (100, 100, 100); // a var c1 vai armazenar um cinzento meio escuro

color c2 = color (200, 0, 0); // a var c2 vai armazenar um vermelho

 

e usar posteriormente estas cores em funções de cor, como o stroke ou o fill

fill (c1); //usar a cor c1 definida anteriormente

fill(c2,20);// usar a cor rgb c2 com um alpha a 20, ie, muito transparente

 

as imagens em processing são definidas com o tipo PImage, e constroiem-se normalmente a ler ficheiros do disco com a função loadImage. vejam neste exemplo nativo do processing, o uso da variável a, da função loadImage e da função image.

 

 

PImage a;  // Declare variable “a” of type PImage

 

void setup() {

  size(200, 200);

  // The file “jelly.jpg” must be in the data folder

  // of the current sketch to load successfully

  a = loadImage(“jelly.jpg”);  // Load the image into the program  

  noLoop();  // Makes draw() only run once

}

 

void draw() {

  // Displays the image at its actual size at point (0,0)

  image(a, 0, 0); 

  // Displays the image at point (100, 0) at half of its size

  image(a, 100, 0, a.width/2, a.height/2);

}

 

alterámos este exemplo, de maneira a manipular a posição da imagem mais pequena, onde comentámos a linha noLoop() e na segunda função image substituímos o 100 e 0 da localização por mouseX e mouseY. o resultado é que a imagem mais pequena anda onde o rato se mexe.

armazenamos a imagem que queremos usar na variável a. há uma função/método do tipo PImage que permite aceder aos valores dos pixeis, ou de regiões da imagem. é a função get e é chamada como função membro de uma imagem, ie, 

 

color c3 = a.get(x,y);

 

aqui uma variável do tipo color vai armazenar a informação rgb do pixel x, y da imagem a. através deste mecanismo, conseguimos aceder aos valores dos pixeis, e criar zonas de comportamentos distintos com base na informação de cor que analisamos.

 

o primeiro sketch que analisamos com este comportamento foi o pointillism, exemplo que vem com o processing. nesse sketch vê-se claramente e de uma forma simples como se acede ao valor de cor de determinado pixel, e se usa esse valor para representar uma ellipse com a cor que é analisada na imagem, criando uma versão pontilhística da imagem base.

 

vamos agora usar esta mecânica de aceder às imagens para criar agentes autónomos que chamei imgTravellers, onde criamos primeiro uma classe com algumas variáveis, e os travellers quando estiverem em regiões totalmente pretas da imagem vão andar em linha recta, na direcção actual que levam; se atingem regiões onde a imagem base é não preta, vão começar a curvar numa dada direcção.

 

o seguir uma determinada direcção, ou determinado ângulo, é obtido, mais uma vez, através das nossas variáveis polares, onde mantemos um ângulo ao longo do qual se anda, e o valor de raio corresponde à magnitude de pixeis que andamos nessa direcção.

 

px = px + cos(angulo)*raio;

py = py + sin(angulo)*raio;

 

para alterar a direcção, basta ir alterando a variável angulo, acumulando-a com valores positivos para efectuar rotações no sentido dos ponteiros do relógio, e valores negativos para rotações de direcção no sentido anti-clockwise.

 

a imagem base que serve de referência e a imagem inicial gerada pelos travellers, comportamento que vamos analisar já de seguida

cloud_3cor

traveller-000487

 

a classe dos travellers vai manter o estado da posição actual e variáveis que definem a direcção e a força com a qual progridem para determinadas regiões espaciais. ela vai ser definida pelas seguintes variáveis:

 

class imgTraveller{

 

  float energy,energy_dec;

 

  float px,py;

  float ppx,ppy;

 

  float force; //vel na dir do angulo

  float angulo;

  float angulo_inc;

  color cor;

 

 

e a função draw, igual ao que já vimos nos escrivas da sessão passada, executa-se se a energia for superior a zero.

 

  void draw(){

    if(energy>0.){

      update();

      stroke(cor,energy);

      line(ppx,ppy,px,py);

    } 

  }

 

 

o segredo vai estar na função update(), onde vamos analisar a cor e bifurcar os tipos de movimentos efectuados em função dessa cor analisada.

 

 

  void update(){

    // 1. diminuir a energia

    energy = energy – energy_dec;

 

    // 2. nova pos

    // 2.1 se preto manter dir, se branco alterar dir

    ppx = px;

    ppy = py;

 

    cor = img.get((int)px,(int)py); //saber o pixel da posição

 

    float g = green(cor); // +eficiente myColor >> 8 & 0xFF;

//    println(g);

 

    if((int)g>0){

     // float f = map(g,0,255,0.1,2.);

      angulo += angulo_inc;//*f;

      px += cos(angulo)*force; 

      py += sin(angulo)*force; 

 //     cor = 255;

    } else {

      px += cos(angulo)*force;

      py += sin(angulo)*force; 

//      cor = 0;

    }

 

    //wrap das coordenadas

    if(px>width){

      px-=width;

      ppx = px;

    }

    if(px<0){

      px+=width;

      ppx = px;

    }

    if(py>height){

      py-=height;

      ppy = py;

    }

    if(py<0){

      py+=height;

      ppy = py;

    }

 

 

  }

 

 

ao passar em cima de um determinado pixel da imagem base, o traveller vai armazenar o valor rgb desse pixel na sua variável cor. de seguida, criamos uma variável local g que vai saber a quantidade de verde nessa cor. se for maior que zero, sabemos que é diferente de preto, logo, incrementamos o angulo do traveller e ele começa a curvar. se a variável g for 0, o bloco de código executado já não tem a linha de acumulação do ângulo, portanto, o traveller vai seguir em frente na direcção actual em que se encontra. 

 

o bloco de código comentado se o traveller se encontra numa região diferente de preto corresponde a criarmos um parâmetro que faz com que ele curve mais ou menos dependendo da intensidade do verde. (criamos uma var f que vai ser um map de g, e depois multiplica-se o ângulo a incrementar por f — assim conseguimos diferentes níveis de curvatura, consoante os mapas de pixeis que usamos de base)

 

a última parte do código da função update faz um wrapping toroidal aos travellers: se eles desaparecerem do lado esquerdo, re-aparecem do lado direito, e o mesmo para o eixo dos y.

 

o comportamento essencial destes travellers fica definido nesta função update, que faz parte da classe que estamos a definir. se usarem imagens de base a cores sem preto, convém subir o valor de treshold em torno do qual bifurcamos o comportamento dos travellers. em vez de ter

 

    if((int)g>0){

 

deve-se subir o valor 0 para um valor inferior a 255, sendo que esse valor é que vai controlar a bifurcação do comportamento do traveller. estes travellers vão agora desenhando linhas na tela da cor exacta do pixel que eles analisam que está na base da imagem bitmap que serve como referência ao comportamento destes agentes autónomos.

 

mais uma vez, definimos a class de um traveller. isto agora permite-nos criar uma array de travellers, em que cada um vai correr as mesmas funções definidas na sua classe, e armazenar posições e velocidades diferentes, pois o construtor instancia valores aleatórios para cada um deles.

 

traveller-001135

traveller-001801

 

as variáveis principais do nosso programa vão então ser a array dos travellers e uma imagem que vai servir de base:

 

PImage img;

 

imgTraveller travellers[];

 

 

na função setup() lemos a imagem e inicializamos os travellers:

 

void setup(){

  size(700,700);

  img =loadImage(“cloud_hole.jpg”);

  background(140);

 

  //init travellers

  travellers = new imgTraveller[100];

  for(int i=0; i<travellers.length;i++){

    travellers[i] = new imgTraveller(random(width),random(height));

  }

 

}

 

 

a função draw() corre os travellers, e cria novos se o rato entrar em cena e se a energia de algum dos travellers estiver menor que 0.

 

void draw(){

  for(int i=0; i<travellers.length;i++){

    travellers[i].draw();

  } 

 

  if(mousePressed){

    for(int i=0; i<travellers.length;i++){

      if(travellers[i].energy<0.){

        travellers[i] = new imgTraveller(mouseX,mouseY);

        break;

      }

    } 

 

  }

 

}

 

 

 

no primeiro sketch eles decaiem, ie, têm uma energia que vai sendo subtraida a cada frame que passa, e é necessário com o rato criar novos travellers em cena.

 

o segundo sketch, já tem menos travellers, mas agora o seu comportamento nunca decai, ficam perpetuamente na tela a escrever. isto porque altera-se o construtor dos travellers e colocamos o valor fixo de 0 na variável que faz decair a energia.

 

energy_dec = 0.;

(…)

update(){

    energy = energy – energy_dec;

(…)

}

 

e vamos no segundo sketch usar um companheiro matemático muito próximo das arrays que é a operação % (lê-se módulo). o módulo devolve o resto da divisão de um número pelo outro, e é usado para garantir que nunca excedemos o limite que criámos na nossa array. temos de criar uma nova variável que serve como cabeça de leitura, e sempre que clickamos no rato, incrementamos essa cabeça, mas temos de ter cuidado para nunca exceder o limite de objectos que usamos na nossa array.

 

  if(mousePressed){

    travellers[headTraveller] = new imgTraveller(mouseX,mouseY);

    headTraveller = (headTraveller+1) % travellers.length;

  }

 

se o rato for pressionado, criamos um novo traveller na posição actual do rato, na posição actual da cabeça na array (a variável global headTraveller) e logo a seguir incrementamos essa variável um valor, passando-a pelo módulo do número de elementos da array, para ter a certeza que headTraveller nunca é superior ou igual a esse valor, pois se for, crasham o vosso programa, com o erro index out of bounds. experimentem comentar o fim da expressão fazendo-a igual a:

 

   headTraveller = (headTraveller+1) ; //% travellers.length;

 

e cliquem no rato, que vão ter esse crash, de certeza.

 

o sketch p5_image_traveller3 vai ser mais depurado graficamente. primeiro alteramos o comando gráfico dos travellers, em vez de uma linha é uma ellipse e essa ellipse tem uma cor de preenchimento branca quase transparente, com a cor preta semi-opaca exterior definida com o stroke. depois, criamos uma variável rad dentro do objecto e a partir do update, dependendo do valor green do pixel, alteramos o raio da ellipse, e parece que as pequenas ellipses no ecran andam a subir e a descer montes, consoante vão a regiões mais claras ou mais escuras da imagem base.

imagetraveller3-0001036

imagetraveller3-0002763

 

os comandos que executam estas alterações no comportamento dos travellers são:

 

   float g = green(cor); // +eficiente myColor >> 8 & 0xFF;

 

    rad = g * 0.25 + 1;

 

mapeamos a variável rad consoante os valores de verde do pixel. e no draw:

 

    if(energy>0.){

      update();

      stroke(cor,energy);

      fill(255,10);

      ellipse(px,py,rad,rad);

    } 

 

 

com estas linhas dos três sketchs e suas variações, pedi-vos então que fizessem um exercício onde alteram a imagem de fundo que serve de base aos travellers, alterando as cores, os comportamentos de curvatura e formas gráficas deles. os resultados gráficos estão nos posts de imagens da sessão 3.

 

 

 

2. máquinas jogáveis, parte 2

estas máquinas de jogar de hoje, primeiro são mais programas de desenhar, e só depois é que os tornamos mais interactivos. como comecei por dizer no início deste post, vamos usar o input sonoro dos computadores através dos microfones internos ou line in (basta que isso esteja definido como preferência do sistema).

 

o som corresponde a um input dinâmico onde as vibrações das moléculas no ar são traduzidas em voltagens e posteriormente sampladas digitalmente. os microfones enviam estas voltagens e o computador através do analog to digital converter (adc) converte essas voltagens contínuas em valores numéricos numa stream contínua a 44100 valores por segundo (ou outro valor, dependendo da sampling rate). estes valores oscilam entre -1. e +1. a primeira parte do sketch snd_tri de hoje corresponde ao exemplo que vem com a biblioteca de som built-in do processing, e visualizamos directamente os buffers de som que chegam ao microfone, temos uma respresentação gráfica das ondas sonoras, apenas por lermos os vários valores da array do som e os representarmos no ecran. (o exemplo a que me refiro é o get line in, que está em file > examples > libraries > minim).

 

para ter som a entrar no nosso sketch de processing, temos de primeiro importar a library minim, para podermos aceder às suas funções e tipos de dados. depois criamos os objectos minim do tipo Minim e in do tipo AudioInput. começamos a cadeia de som na função setup, ao declararmos minim = new Minim(this);  e logo a seguir configuramos a variável in para receber a stream de input de som com a linha in = minim.getLineIn(Minim.STEREO, 512);.

 

a partir de agora, podemos ter acesso aos valores que estão nos buffers left, right, ou mix do nosso input sonoro, representado pela variável in. a função  in.left.get(i) vai buscar o valor da sample i do canal esquerdo do audio input. é através deste mecanismo que o exemplo desenha as waveforms directamente no ecran, e temos imediatamente uma respresentação gráfica dos valores sonoros. mas agora a função principal que nos interessa, e mais simples, corresponde à função level, de um dos canais da stream, ou do mix dos canais, que vai buscar o valor da energia sonora em rms. (root mean squared). este valor de rms oscila entre 0. e 1. e varia de acordo com a presença de energia sonora no espaço capturado pelos microfones. não é um valor que indique frequências e magnitude de som, para isso temos de efectuar análise por transformadas de fourrier ao sinal (fft’s), é apenas um valor que corresponde à soma dos quadrados de todos os elementos do buffer, aos quais vamos saber qual é o valor médio, aplicando posteriormente a divisão por N elementos e ainda a raiz quadrada em cima deste valor. (http://en.wikipedia.org/wiki/Root_mean_square)

 

este valor já nos dá pano para mangas, para fazermos coisas interessantes com o som, e vamos começar hoje a construir interacções à base deste valor.

 

no primeiro sketch snd_tri, vamos ter um triângulo a mover-se na tela com o comportamento de uma máquina de escrever, onde ele percorre a velocidade constante linhas horizontais, e quando chega ao fim dessa linha, colocamos a posição x novamente a zero, no ínicio, e incrementamos a posição y mais 100 pixeis, para irmos para a linha de baixo. se por acaso a posição y salta para além de altura da tela, repomos o valor de py no zero da nossa tela, neste caso o valor 300, pois guardamos os primeiros 300 pixeis para representar a onda sonora.

 

isto porque apenas mantemos o estado de um centro que se move no ecran, centro esse representado por px e py. fazemos mais abaixo o triângulo, calculado três pontos a, b e c localizados a deteminado raio do centro e com ângulos distintos entre eles. o ponto a estará com um ângulo de PI/2, o ponto b com angulo de PI+PI/4 e o c 2*PI-PI/4.

sndtri1-002310

sndtri1-002562

aqui se segue o código completo do programa. experimentem, como vimos na aula, alterar os valores do filtro lowpass, de maneira a ser mais reactivo mas não tão ríspido, e com base nos vossos valores de audio, façam o fine-tune desse parâmetro.

 

/// SND_TRI // o som do pensamento // 2009

// import a library

import ddf.minim.*;

 

// um objecto da biblioteca

Minim minim;

// o objecto do input sonoro

AudioInput in;

 

// o centro do triângulo

float px = 0, py = 300;

// o tamanho do triangulo

float rad=50;

// a inclinação do triângulo

float angulo;

// o valor do audio para ser passado por um filtro

float audio_energy;

 

 

 

void setup(){

  size(700,700);

  minim = new Minim(this);

  minim.debugOn();

  // get a line in from Minim, default bit depth is 16

  in = minim.getLineIn(Minim.STEREO, 512);

  background(0);

}

 

 

 

void draw(){

  //  background(0);

  fill(0,10);

  noStroke();

  rect(0,0,width,200); // só apaga até à posição 200 y

 

  stroke(255,150);

 

  // draw the waveforms

  for(int i = 0; i < in.bufferSize() – 1; i++){

    line(i, 50 + in.left.get(i)*50, i+1, 50 + in.left.get(i+1)*50);

    line(i, 150 + in.right.get(i)*50, i+1, 150 + in.right.get(i+1)*50);

  }

 

 

  //desenhar um triângulo a mexer consoante o som

 

  // a posição incrementa-se, qd chega ao fim da linha,

  // passa para a linha de baixo, qd passa da linha de baixo,

  // vem para o início, em loop

 

  px = px + 1.5;

  if(px > width){

    py = py + 100;

    px = 0;

  }

 

  if(py > height)

    py = 300; //zero da posição y

 

  //   o valor da energia do som vai-se usar para escalar o raio do tri

  //   passado com um filtro lowpass para suavizar as transições

 

  float rms = in.mix.level();

  float f = 0.1;

  audio_energy = audio_energy * (1.-f) + rms * f;

 

//  println(“audio energy “+audio_energy);

 

  // um triângulo são 3 pontos

  // cada ponto está à mesma distância do centro

 

  float pt1x,pt1y;

  float pt2x,pt2y;

  float pt3x,pt3y;

  float a; // um angulo

 

  rad = map (audio_energy, 0. , 0.2 , 50, 250);

  angulo += 0.01;

 

  a = HALF_PI + angulo;

  pt1x = cos(a)*rad + px; 

  pt1y = sin(a)*rad + py; 

 

  a = PI + PI/4 + angulo;

  pt2x = cos(a)*rad + px; 

  pt2y = sin(a)*rad + py; 

 

  a = TWO_PI – PI/4 + angulo;

  pt3x = cos(a)*rad + px; 

  pt3y = sin(a)*rad + py; 

 

 fill(255,100);

 stroke(0,150);

 

 triangle(pt1x,pt1y, pt2x,pt2y, pt3x, pt3y);

 rect(px,py,2,2);

 

}

 

 

 

de seguida, o sketch snd_tri2, torna mais parâmetros reactivos à energia sonora. a velocidade no eixo dos x, a variação do ângulo, e mantemos também a variação do raio, tudo isto usando sempre o mesmo valor do audio_energy e mapeando-o para universos âmbito onde as variáveis se movem. as alterações são:

 

  px = px + map(audio_energy,0.,0.2,1.,25.);

 

  angulo += map(audio_energy,0.,0.2,0.01,0.2);

 

alteramos já bastante o comportamento do programa com a alteração destas duas linhas. 

sndtri2-0006631

sndtri2-001770

os sketches snd_tri3 e snd_tri4 correspondem aos dois passos essenciais para tornar este comportamento à base de som como input para um sistema jogável, e tornam as coisas mais interessantes. são também exemplos que tenho investigado e construído para realizar comportamentos gráficos à base de som, criando sistemas jogáveis onde o único input é a intensidade sonora, e tenho obtido resultados bem motivantes. 

 

fizemos apenas umas modificações no sketch, onde agora o comportamento do triângulo é o de rodar continuamente se não houver som, ou se o valor do som estiver a baixo de um threshold baixo, e se houver intensidade sonora, o triângulo deixa de rodar e começa a andar nessa direcção, com uma magnitude igual à energia sonora e aqui as coisas começam a tornar-se interessantes. com a voz ou com os sons no espaço conseguimos controlar um comportamento de um triângulo e guiá-lo para determindos sítios na tela digital. as linhas de código que permitem isso simples e são:

 

  // agora o valor do som incrementa a posição do triângulo

  // consoante a sua orientação actual

  if(audio_energy < 0.01)

    angulo += 0.02;

 

  float walk = map(audio_energy,0.,0.2,0.,25.);

 

  // + half_pi para corrigir a orientação ao longo da qual o tri anda

  px = px + cos(angulo+HALF_PI)*walk;

  py = py + sin(angulo+HALF_PI)*walk;

 

 

o exemplo mais simples que trouxe foi agora realizando o snd_tri4, onde insiro uma class que representa os compridos amarelos inspirados no pacman, e quando a distância do triângulo ao comprimido é inferior à soma dos raios, significa que comemos o comprido, e logo surge outro na tela para irmos apanhar. e voilà, um mini-jogo interactivo à base de som, com mais umas variavéis fica finalizado. vamos continuar a analisar máquinas de desenhar à base de som, e construir algumas experiências com base nos conceitos que temos vindo a abordar.

sndtri4-002775

sndtri4-006319

sndtri4-008309

até para a semana. leiam o livro do processing, para reverem as noções de programação que temos estado a analisar.

Anúncios

One Trackback/Pingback

  1. By sessão 17 « O Som do Pensamento on 27 Maio 2009 at 11:54 am

    […] primeiras noções de interactividade usando os microfones ligados ao computador, samplando a energia rms sonora, ou um espectro fft que nos dá intensidade por banda de frequência. aplicar os parâmetros do áudio em variáveis que controlam gráficos. noções de classes mais avançadas com os exemplos dos triângulos de som. (sessão 3) […]

Deixe uma Resposta

Preencha os seus detalhes abaixo ou clique num ícone para iniciar sessão:

Logótipo da WordPress.com

Está a comentar usando a sua conta WordPress.com Terminar Sessão / Alterar )

Imagem do Twitter

Está a comentar usando a sua conta Twitter Terminar Sessão / Alterar )

Facebook photo

Está a comentar usando a sua conta Facebook Terminar Sessão / Alterar )

Google+ photo

Está a comentar usando a sua conta Google+ Terminar Sessão / Alterar )

Connecting to %s

%d bloggers like this: