viernes, 13 de septiembre de 2013

ALGUNOS CÓDIGOS USANDO LA LIBRERÍA BEADS

Por: Diego Andrés Restrepo Leal

Cordial saludo.

A continuación se compartirá información de códigos desarrollados en processing (el cual puede ser descargado en http://processing.org/) implementando la librería beads. La cual pueden obtener en http://www.beadsproject.net/

Los códigos que se presentaran a continuación son ejemplos y/o están inspirados en ejemplos del libro titulado "Sonifying Processing: The Beads Tutorial" el cual fue escrito por: Evan X. Merz este documento se puede encontrar en http://computermusicblog.com/blog/sonifyingprocessing/

La librería Beads se trabajan con unidades generadores o Unit Generators los cuales son los “bloques” o “cajitas” (de un diagrama de bloques) una analogía apropiada es verlos como los pedales que usa un guitarrista para modificar el sonido de su guitarra, en donde cada pedal es una unidad generadora. (Ver capitulo 2 sección 2.1 de Sonifying Processing: The Beads Tutorial)

El pilar de un programa usando Beads es el objeto AudioContext con este se hace una conexión con el hardware de audio y asigna memoria para el procesamiento de audio. El procesamiento de audio se inicia con la rutina: ac.start. (Ver capitulo 2 sección 2.2 de Sonifying Processing: The Beads Tutorial)

Los objetos básicos de la librería Beads son:
Gain: Se usa para controlar el volumen de una señal de audio.
WavePlayer: Se usa para generar señales de audio periódicas simples.
Glide: se utiliza para enviar los datos numéricos a otras beads.
(Ver capitulo 2 sección 2.3 de Sonifying Processing: The Beads Tutorial)


Ejemplo: Code Listing 2.3.1. Hello_Sine.pde

import beads.*; //Importa la librería.
AudioContext ac; // crea el AudioContext

void setup()
{
size(400, 300);
ac = new AudioContext();// inicializa el AudioContext.
WavePlayer wp = new WavePlayer(ac, 440, Buffer.SINE); // Crea una WavePlayer
// WavePlayer es el objeto que genera la forma de onda.
ac.out.addInput(wp); // conecta WavePlayer to el AudioContext
ac.start();//Inicia el procesamiento.
}

WavePlayer tiene tres parámetros: el AudioContext, la frecuencia y la forma de onda.
(Ver capitulo 2 sección 2.3.1 de Sonifying Processing: The Beads Tutorial)

Para muchas personas que han trabajado generando sonidos con PIC’s (por ejemplo), han tenido inconvenientes al tratar de generar dos o más señales que suenen al tiempo. Esto no es problema en Processing ya sea que se esté usando la librerías como Minim o Beads. 

A continuación un código ejemplo en el cual se ejecuta cuatro señales y se escuchan al mismo tiempo.

import beads.*;
AudioContext ac;
WavePlayer wp,wp2,wp3,wp4; //Crea cuatro objetos WavePlayer
Gain g; //Crea un objeto Gain, con el cual se controla el volumen.

void setup()
{
  size(400,400);
  ac = new AudioContext();
  // “Bloque”  generador de señales.
  wp  = new WavePlayer(ac,440,Buffer.SINE);
  wp2 = new WavePlayer(ac,277.18,Buffer.SINE);
  wp3 = new WavePlayer(ac,329.63,Buffer.SINE);
  wp4 = new WavePlayer(ac,220,Buffer.SINE);

  /*******************************************************************
En el “bloque” anterior se generan cuatro señales periódicas senoidales las cuales son:
wp con una frecuencia de 440Hz, wp2 con una frecuencia de 277.18Hz y de este modo wp3=329.63Hz y wp4=220Hz
************************************************************************/
//”Bloque” de control de volumen.
  g  = new Gain(ac, 4, 0.2);
/***************************************************************************
En el “bloque” anterior se generan cuatro unidades de ganancia a la cual se conectaran una de las señales.
***************************************************************************/

  g.addInput(wp); //Se conecta wp a g
  g.addInput(wp2); //Se conecta wp2 a g
  g.addInput(wp3); //Se conecta wp3 a g
  g.addInput(wp4); //Se conecta wp4 a g

  ac.out.addInput(g); //Se conecta g a la “salida”

  ac.start();
}
void draw()
{
}

Otra forma de escribir el código:

import beads.*;
AudioContext ac;
WavePlayer wp;
Gain g;

float s1=220,s2=277.18,s3=329.63,s4=440; //Se definen las cuatro frecuencias

void setup()
{
  size(400,400);
  ac = new AudioContext();

  sonido(s1); //Se invoca a la función sonido y se le ingresa el argumento s1 el cual es la frecuencia.
  sonido(s2);
  sonido(s3);
  sonido(s4);

  ac.start();
}
void draw()
{
}
void sonido(float frec)
{
  wp  = new WavePlayer(ac,frec,Buffer.SINE); //Se genera la señal.
  g = new Gain(ac, 2, 0.2); //Se establece la ganancia el volumen.
  g.addInput(wp); //Se conecta la señal con la unidad de ganancia.
  ac.out.addInput(g); // se conecta la unidad de ganancia con la “salida”.
}

A continuación se mencionará la síntesis de sonidos en processing usando la librería Beads. Para encontrar información más detallada se sugiere que el lector revise el tercer capítulo del libro: “Sonifying Processing: The Beads Tutorial”.

Una analogía de la síntesis coger varios pedazos pequeños o sencillos y construir algo grande o complejo con ellos.

Se hablará un poco sobre la síntesis aditiva. En este modelo o forma de síntesis se combinan dos o más sonidos de forma aditiva (se añaden o se “suman”) para producir un sonido más complejo.

A continuación se presentará el ejemplo: Code Listing 3.2.1. Additive_01.pde

import beads.*;

AudioContext ac;

WavePlayer wp1;
Glide frequencyGlide1;

WavePlayer wp2;
Glide frequencyGlide2;

Gain g;

void setup()
{
  size(400,300);

  ac = new AudioContext();
  //Crea un objeto con un valor inicial de 20Hz y un “corrimiento” en tiempo de 50ms
  frequencyGlide1 = new Glide(ac,20,50);
  wp1 = new WavePlayer(ac,frequencyGlide1,Buffer.SINE); /* Genera la señal cuya frecuencia depende de frequencyGlide1*/

  frequencyGlide2 = new Glide(ac,20,50);
  wp2 = new WavePlayer(ac,frequencyGlide2,Buffer.SINE);

  g = new Gain(ac,1,0.5);

  g.addInput(wp1);
  g.addInput(wp2);

  ac.out.addInput(g);
  ac.start();
}

void draw()
{
/* Actualiza la frecuencia sobre la base de la posición del cursor del ratón dentro de la ventana de procesamiento*/
  frequencyGlide1.setValue(mouseY);
  frequencyGlide2.setValue(mouseX);
}

En el ejemplo anterior se observó la síntesis aditiva de dos señales controladas por el mouse.
(Ver capitulo 3 sección 3.2.1 de Sonifying Processing: The Beads Tutorial)


Ahora se presentará un ejemplo de una síntesis aditiva en el cual en vez de combinar varias señales senoidales a frecuencias no relacionadas, se combinaran señales senoidales que son múltiplos de la frecuencia más baja.

Código ejemplo:  Code Listing 3.2.2. Additive_02.pde

import beads.*;

AudioContext ac;

float baseFrequency = 200.0f;//Frecuencia fundamental
int sineCount = 10;

//Declara los generadores de unidad, los [] significan que son Arrays
WavePlayer sineTone[];
Glide sineFrequency[];
Gain sineGain[];

Gain masterGain;
void setup()
{
  size(400, 300);
  ac = new AudioContext();
  masterGain = new Gain(ac, 1, 0.5);//Volumen maestro
  ac.out.addInput(masterGain);
  //Se inicializan nuestros objetos Arrays
  sineFrequency = new Glide[sineCount];
  sineTone = new WavePlayer[sineCount];
  sineGain = new Gain[sineCount];

  float currentGain = 1.0f;
  for( int i = 0; i < sineCount; i++)
  {
    //Crea el objeto Glide que controlará de frecuencia del WavePlayer
    sineFrequency[i] = new Glide(ac,baseFrequency * (i + 1),30);
    sineTone[i] = new WavePlayer(ac,sineFrequency[i],Buffer.SINE);
    sineGain[i] = new Gain(ac, 1, currentGain); //Crea el Gain del objeto
    sineGain[i].addInput(sineTone[i]);
    masterGain.addInput(sineGain[i]);
    //Reduce la ganancia para la próxima señal
    currentGain -= (1.0 / (float)sineCount);
  }
  ac.start();
}

void draw()
{
  //Actualiza la frecuencia fundamental, depende de la posición de mouse. Añade 20Hz.
  baseFrequency = 20.0f + mouseX;
  for( int i = 0; i < sineCount; i++)
  {
   //Actualiza la frecuencia de cada señal.
    sineFrequency[i].setValue(baseFrequency * (i + 1));
  }
}

El próximo método de síntesis que se mencionará es la síntesis modular.
(Ver capitulo 3 sección 3.3 de Sonifying Processing: The Beads Tutorial)

Lo que se busca en la síntesis modular es que a partir de una señal llamada modulador se genera otra señal llamada portadora. El modulador controla algún parámetro de la señal portadora, por ejemplo, su amplitud.

Código ejemplo: Code Lising 3.3.2. Frequency_Modulation_02.pde

import beads.*;
AudioContext ac;

WavePlayer modulator;
Glide modulatorFrequency;
WavePlayer carrier;
Gain g;

void setup()
{
  size(400, 300);
  ac = new AudioContext();

  modulatorFrequency = new Glide(ac, 20, 30);
  modulator = new WavePlayer(ac,modulatorFrequency,Buffer.SINE);

  Function frequencyModulation = new Function(modulator)
  {
    public float calculate()
    {
      /*****************************************************************************
     return x [0], es el valor original de la señal moduladora (una onda sinusoidal) multiplicado por   200.0        para que el seno varía entre -200 y 200 el número 200 aquí se llama el "índice de modulación" cuanto mayor es el índice de modulación que, el más fuerte las bandas laterales se añaden mouseY, por lo que varía de mouseY - 200 a + 200 mouseY.
*******************************************************************************/
      return (x[0] * 200.0) + mouseY;
    }
  };

  carrier = new WavePlayer(ac,frequencyModulation,Buffer.SINE);
  g = new Gain(ac, 1, 0.5);
  g.addInput(carrier);
  ac.out.addInput(g);
  ac.start();
}

void draw()
{
  modulatorFrequency.setValue(mouseX);
}

El anterior fue un sencillo ejemplo de la síntesis modular, a continuación se mostrará un pequeño ejemplo de la síntesis ring según Evan X. Merz: “Es como multiplicar dos señales senoidales” (Ver capitulo 3 sección 3.3.3 de Sonifying Processing: The Beads Tutorial)

Código ejemplo: Code Listing 3.3.3. Ring_Modulation_01.pde

import beads.*;
AudioContext ac;

WavePlayer modulator;
Glide modulatorFrequency;
WavePlayer carrier;
Glide carrierFrequency;
Gain g;

void setup()
{
  size(400, 300);
  ac = new AudioContext();

  modulatorFrequency = new Glide(ac, 20, 30);
  modulator = new WavePlayer(ac,modulatorFrequency,Buffer.SINE);
  carrierFrequency = new Glide(ac, 20, 30);
  carrier = new WavePlayer(ac,carrierFrequency,Buffer.SINE);
/******************************************************************************
Esta es una función personalizada de modulación ring.
Modulation = Modulator[t] * Carrier[t]
Para obtener más información sobre las funciones personalizadas (Ver apéndice A, sección A.1. de Sonifying Processing: The Beads Tutorial).
*******************************************************************************/
  Function ringModulation = new Function(carrier, modulator)
  {
    public float calculate()
    {
      return x[0] * x[1]; //Se multiplican ambas señales.
     }
  };

  g = new Gain(ac, 1, 0.5); //Se crea la unidad controladora del volumen
  g.addInput(ringModulation); //Se conecta la salida de ringModulation a g
  ac.out.addInput(g); //Se conecta g a la salida de audio
  ac.start();
}

void draw()
{
//Estable las frecuecnias del modulador y del portador dependiendo de la posición del mouse.
  carrierFrequency.setValue(mouseY);
  modulatorFrequency.setValue(mouseX);
}

Un objeto muy útil es el Envelove con el cual se puede controlar el ataque y decaimiento de algún parámetro de otros objetos. Uno de sus usos más comunes es el control de la amplitud.
EL objeto Envelove normalmente a de 0.0 a 1.0. (Ver capitulo 3 sección 3.4de Sonifying Processing: The Beads Tutorial)


En el código ejemplo que se presentará a continuación se implementará el objeto envelove en la síntesis modular. El objeto envelove controlará la amplitud de la señal producida por la síntesis.

Código ejemplo: Code Listing 3.4.1. Frequency_Modulation_03.pde

import beads.*;
AudioContext ac;

WavePlayer modulator;
Glide modulatorFrequency;
WavePlayer carrier;

Envelope gainEnvelope; // Se crea el objeto envelove
Gain synthGain;
void setup()
{
  size(400, 300);
  ac = new AudioContext();
  modulatorFrequency = new Glide(ac, 20, 30);
  modulator = new WavePlayer(ac,modulatorFrequency,Buffer.SINE);

  Function frequencyModulation = new Function(modulator)
  {
    public float calculate()
    {
      return (x[0] * 100.0) + mouseY;
    }
  };

  carrier = new WavePlayer(ac,frequencyModulation,Buffer.SINE);
  gainEnvelope = new Envelope(ac, 0.0);
  synthGain = new Gain(ac, 1, gainEnvelope);
  synthGain.addInput(carrier);

  ac.out.addInput(synthGain);
  ac.start();
  background(0);
  text("Click to trigger the gain envelope.", 100, 120);
}

void draw()
{
  modulatorFrequency.setValue(mouseX);
}

void mousePressed()
{
/*******************************************************************************
Cuando se click izquierdo sobre el lienzo, se añade un segmento de ataque de 50 ms al envelove y un segmento de decaimiento 300 ms al envelove.
*******************************************************************************/
  gainEnvelope.addSegment(0.8, 50); // Mayor a 50ms sube a 0.8
  gainEnvelope.addSegment(0.0, 300); // En 300ms decae a 0.0
}

Los ejemplos anteriores son muestras de los métodos de síntesis que se puedes efectuar con la librería beads ahora se mostrara un código que contiene varios ejemplos de los efectos que se le pueden dar a los sonidos.

Ejemplo:

En el siguiente código se implementará los efectos de compresión, delay y reverberación.

import beads.*;

AudioContext ac;
//Crea las unidades generadoras
WavePlayer n;
Gain gn,delayGain,masterGain;
TapIn delayIn;
TapOut delayOut;
Reverb r;
Compressor c;
Envelope gainEnvelope;
/* Arrays en donde se encuentran frecuencias que corresponden a cuatro octavas de la escala temperada */
float[] E={82.40,164.81,329.62,659.25};

float[] F={87.30,174.61,349.22,698.45};
float[] Fs={92.49,184.99,369.99,739.98};

float[] G={97.99,195.99,391.99,783.99};
float[] Gs={103.82,207.65,415.30,830.60};

float[] A={110.0,220.0,440.0,880.0};
float[] As={116.54,233.08,466.16,932.32};

float[] B={123.47,246.94,493.88,987.76};

float[] C={130.80,261.62,523.25,1046.50};
float[] Cs={138.59,277.18,554.36,1108.73};

float[] D={146.83,293.66,587.32,1174.65};
float[] Ds={155.56,311.12,622.25,1244.50};

void setup()
{
  size(400,400);
  ac = new AudioContext();
  //Acorde de La mayor
/*Al ejecutarse cuatro tonos suenan al tiempo, en donde A[0] es el más grave y corresponde a una frecuencia de 110.0Hz y E[2] es el más agudo de los cuatro y corresponde a una frecuencia de 329.62HZ*/
  nota(A[0]);
  nota(A[1]);
  nota(Cs[1]);
  nota(E[2]);

  ac.start();
}

void draw()
{

}

void nota(float frec)
{
  n = new WavePlayer(ac,frec,Buffer.SINE);
  gn = new Gain(ac, 1, new Envelope(ac, 0.1));
  ((Envelope)gn.getGainEnvelope()).addSegment(0, 1000, new KillTrigger(gn));
  gn.addInput(n);
  compresor(gn);
}

void compresor(Gain ci)
{
  c = new Compressor(ac,1); // Crea un nuevo compresor son una sola salida
  c.setAttack(30); // Define el tiempo de ataque del compresor
  c.setDecay(200); // off, una vez que se cruza el umbra.
  c.setRatio(4.0); // Proporción de la compresión
  c.setThreshold(0.6); // Umbral en el que comienza la compresión
  c.addInput(ci);
  ac.out.addInput(c);
/****************************************************************************
Se recomienda al lector que pruebe el código sólo con el compresor, luego lo convine
con la función reverbera() y retardo(), inicial mente estas dos funciones están comentariadas
para que sea más fácil percibir la diferencia. Lo que se debe hacer es des-comentariarlas para hacer las pruebas.
****************************************************************************/
  //reverbera(c);
  //retardo(c);
}

void reverbera(Compressor gr)
{
  r = new Reverb(ac,1); //Crea un nuevo reverb son una sola salida
  r.setSize(0.7); // Ajuste el tamaño de la habitación (entre 0 y 1) 0.5 es el predeterminado
  r.setDamping(0.5);//Se ajuste de la amortiguación (entre 0 y 1)
  r.addInput(gr); // Se conecta la salida de otra unidad generadora a la unidad de reverb
  ac.out.addInput(r);
}
//Delay
void retardo(Compressor gd)
{
/*Se establece el Delay (crear la entrada de Delay) - el segundo parámetro establece el tiempo máximo de retardo en milisegundos*/
  delayIn = new TapIn(ac,2000);
  delayIn.addInput(gd); //Se conecta la salida de otra unidad al Delay
/* Crea la salida de Delay - el último parámetro es la longitud del tiempo de retardo inicial en milisegundos*/
  delayOut = new TapOut(ac,delayIn,500.0);
  delayGain = new Gain(ac,1,0.40);
  delayGain.addInput(delayOut);
  ac.out.addInput(delayGain);
}

Ahora se presenta un ejemplo con Arduino. (El código que se incluirá aún está en desarrollo)

En este ejemplo se combina lo aprendido en los ejemplos anteriores.
Se incluye la librería “processing.serial” y “cc.arduino”. (Obtendrá mayor información en: http://playground.arduino.cc/Interfacing/Processing )

Debe estar muy atento al momento de incluir la librería de arduino ya que no “funciona” en todas las tarjetas de arduino por ejemplo el arduino Leonardo, si desea o trabajará con el arduino Leonardo o Mega deberá buscar un poco más, hasta encontrar la librería apropiada.

En el ejemplo al presionar un pulsador (el cual esta conectado al pin12) suena una nota y dejará de sonar cuando se deje de presionar el pulsador.

El pulsador esta conectado a una terminal de la resistencia y a referencia (tierra), la terminal de la resistencia que no esta conectada con el pulsador se conecta a 5v, luego el nodo formado por la conexión del pulsador y de la resistencia va al pin 12 del arduino.

(Puede usarse cualquier pin digital de la tarjeta).

Ejemplo:

import processing.serial.*;
import cc.arduino.*;

import beads.*;

Arduino arduino;

AudioContext ac;

WavePlayer n;
Gain gn;
Reverb r;
Compressor c;
Envelope gainEnvelope;

float[] E={82.40,164.81,329.62,659.25};

float[] F={87.30,174.61,349.22,698.45};
float[] Fs={92.49,184.99,369.99,739.98};

float[] G={97.99,195.99,391.99,783.99};
float[] Gs={103.82,207.65,415.30,830.60};

float[] A={110.0,220.0,440.0,880.0};
float[] As={116.54,233.08,466.16,932.32};

float[] B={123.47,246.94,493.88,987.76};

float[] C={130.80,261.62,523.25,1046.50};
float[] Cs={138.59,277.18,554.36,1108.73};

float[] D={146.83,293.66,587.32,1174.65};
float[] Ds={155.56,311.12,622.25,1244.50};

int sw=12,estasw;

void setup()
{
  size(400,400);
  println(Arduino.list());
  arduino = new Arduino(this, Arduino.list()[0], 57600);
  arduino.pinMode(sw,Arduino.INPUT);

  ac = new AudioContext();
  gainEnvelope = new Envelope(ac, 0.0);

  nota(C[1]);

  ac.start();
}

void draw()
{
  ver();
}

void nota(float frec)
{
  n = new WavePlayer(ac,frec,Buffer.SINE);
  gn = new Gain(ac, 1,gainEnvelope);
  gn.addInput(n);
  compresor(gn);
}

void compresor(Gain ci)
{
  c = new Compressor(ac,1);
  c.setAttack(30);
  c.setDecay(200);
  c.setRatio(4.0);
  c.setThreshold(0.6);
  c.addInput(ci);
  ac.out.addInput(c);
  reverbera(c);
}

void reverbera(Compressor gr)
{
  r = new Reverb(ac,1);
  r.setSize(0.7);
  r.setDamping(0.5);
  r.addInput(gr);
  ac.out.addInput(r);
}

void ver()
{
  estasw=arduino.digitalRead(sw);

  if(estasw==Arduino.HIGH)
  {
    ac.stop();
  }
  else
    {
      ac.start();
      gainEnvelope.addSegment(0.8, 50);     
    }
}

Para mayor información sobre el uso de la librería beads se sugiere consultar al libro "Sonifying Processing: The Beads Tutorial"


Muchas gracias por su atención.

No hay comentarios:

Publicar un comentario