Minim 2.02 PE (cont.)

 

Nuevo generador: “BandLimitedPulse”

BandLimitedPulse es un tipo de generador de señal, similar a los generadores de onda cuadrada, pulsos o dientes de sierra, pero con la diferencia de que su espectro consta de una cantidad exacta de armónicos, todos con la misma amplitud, que se puede especificar al instanciarlo. Matemáticamente, BandLimitedPulse usa lo que se conoce como la “fórmula cerrada” de una Serie de Fourier Truncada, de modo que su consumo de cómputo equivale al de tres sinusoides y una división, cualquiera sea el número de parciales elegido (ver F. Richard Moore “Elements of Computer Music” Prentice-Hall, 1990, pág. 271).

Ejemplo #11 – 2 BandLimitedPulse + 1 PulseWave

  BandLimitedPulse blp1 = new BandLimitedPulse (261.63, .3, 10, sr);  // 10 = número de parciales
  PulseWave pulse = new PulseWave (.1, .5, sr);
  blp1.setAmplitudeModulator (pulse);
  ADSR env1 = new ADSR (20, 200, .1, 100, sr, gate);
  pulse.setFrequencyModulator (env1);
  ADSR env2 = new ADSR (5, 400, .85, 400, sr, gate);
  blp1.setEnvelope (env2);
  BandLimitedPulse blp2 = new BandLimitedPulse (98.12, .7, 5, sr);    // 5 parciales
  SignalClone blp11 = new SignalClone (blp1);
  blp2.setFrequencyModulator (blp1, Oscillator.RELATIVE);
  blp2.setAmplitudeModulator (blp11);
  ADSR env3 = new ADSR (5, 800, .85, 200, sr, gate);
  blp2.setEnvelope (env3);
  SineWave lfo = new SineWave (7.7, .77, sr);
  pulse.setPWM (lfo);
  out1.addSignal (blp2, Oscillator.RELATIVE);

 

Filtros “modulados”

La clase IIRFilter proporciona el modelo para casi todos los filtros que vienen con Minim. Estos filtros están implementados de forma recursiva, lo que los hace bastante eficientes. Introduciendo una pequeña modificación en la clase IIRFilter, habilitamos la posibilidad de variar los parámetros de un filtro en función de una señal de control, tal como lo hicimos antes con Oscillator.

Los métodos agregados son setFrequencyEnvelope, setFrequencyModulator, setBandWidthEnvelope, y setBandWidthModulator. Todos ellos permiten usar cualquier tipo señales (cualquier objeto de una clase que implemente AudioSignal) para variar la frecuencia de corte, en el caso de los dos primeros, y el ancho de banda del filtro, en el caso de los dos restantes (siempre que se trate de un filtro pasabanda).

El funcionamiento de estos métodos tiene cierta similitud con el de sus pares de la clase Oscillator, pero también algunas diferencias con ellos. Igual que en el caso de la modulación en amplitud, la diferencia entre “envelope” y “modulator” aquí, es que “envelope” está preparada para señales de tipo ADSR, que son siempre positivas, y “modulator” en cambio introduce un “DC offset” para señales oscilatorias. Sin embargo, a diferencia de lo que ocurre en Oscillator, no se pueden usar ambos métodos a la vez, es decir, hay una única señal de control para cada parámetro, y es la misma en el caso de “setFrequencyEnvelope” y “setFrequencyModulator“, por ejeplo.

Otra diferencia con el funcionamiento de los moduladores en Oscillator, es que aquí el valor “central” del parámetro del propio objeto -es decir, la propia frecuencia del filtro o su propio ancho de banda- es ignorado, y el rango de acción de las señales de control se especifica con los parámetros “bottom” y “top“, en Hz, que corresponden a la frecuencia mínima y máxima, respectivamente, de la variación. (Nota: los valores de bottom y top pueden intercambiarse, produciendo el mismo efecto que invertir la envolvente; en señales periódicas no se notará la diferencia)

Veamos algunos ejemplos:

Ejemplo #12 – parecido al ejemplo #9 pero con pasabanda modulado

  Adder adder = new Adder(armonico);                        // el mismo sintetizador aditivo del ejemplo #8
  adder.setFreq (130.82);                                   // seteamos la frecuencia fundamental a 130.82Hz (1/2 fc)
  WhiteNoise wn = new WhiteNoise (.16);                     // definimos un ruido blanco
  adder.addSignal (wn);                                     // lo agregamos al Adder
  BandPass bp = new BandPass (2000, 200, sr);               // definimos un filtro pasabanda; 
  adder.addEffect (bp);                                     // agregamos el pasabanda al Adder
  adder.setAmp (2.5/adder.amplitude());                     // normalizamos la amplitud del Adder a 2.5
  ADSR env1 = new ADSR (8, 150, .7, 300, sr, gate);         // definimos las envolventes 1, 2 y 3
  ADSR env2 = new ADSR (1100, 350, .6, 200, sr, gate);
  ADSR env3 = new ADSR (110, 1450, .1, 200, sr, gate);
  BandLimitedPulse crr = new BandLimitedPulse (261.63, .8, 2, sr);   // definimos la portadora, compuesta de 2 sinusoides
  crr.setEnvelope (env1);                                            // asignamos la envolvente 1 a la portadora
  crr.setFrequencyModulator (adder, Oscillator.RELATIVE);   // agregamos el Adder a la portadora como modulador de frecuencia relativo
  bp.setFrequencyEnvelope (env2, 100, 5000);                // asignamos la envolvente 2 a la frecuecia del pasabanda; bottom=100Hz, top=5KHz
  bp.setBandWidthEnvelope (env3, 700, 60);                  // asignamos la envolvente 3 al ancho de banda del pasabanda; bottom=700Hz, top=60Hz
  out1.addSignal (crr, Oscillator.RELATIVE);                // agregamos la portadora a la salida, etc.

Ejemplo #13 – Sintetizador Sustractivo Simple

  ADSR env1 = new ADSR (5, 800, .5, 100, sr, gate);         // envolvente 1 (de amplitud)
  SignalClone env11 = new SignalClone (env1);               // clonamos envolvente 1
  SawWave saw = new SawWave (261.63, .6, sr);               // nueva 'dientes de sierra'
  SquareWave sqr = new SquareWave (130.82, .4, sr);         // nueva onda cuadrada
  saw.setEnvelope (env1);                                   // agregamos la envolvente 1 a la 'dientes de sierra'
  saw.setPan (1);                                           // paneamos la 'dientes de sierra'
  sqr.setEnvelope (env11);                                  // agregamos el clon de la envolvente 1 a la onda cuadrada
  sqr.setPan (-1);                                          // paneamos la onda cuadrada
  ADSR env2 = new ADSR (400, 400, .2, 300, sr, gate);       // envolvente 2 (de frecuencia)
  LowPassFS lp = new LowPassFS (1000, sr);                  // nuevo filtro pasabajos
  lp.setFrequencyEnvelope (env2, 100, 4500);                // asignamos la envolvente 2 a la frecuencia de corte del pasabajos; bottom=100Hz, top=4.5KHz
  out1.addSignal (saw, Oscillator.RELATIVE);                // agregamos la 'dientes de sierra' a la salida
  out1.addSignal (sqr, Oscillator.RELATIVE);                // agregamos la onda cuadrada a la salida
  out1.addEffect (lp);                                      // agregamos el pasabajos a la salida

 

Nuevo Filtro: “LowPassResonant”

No podía faltar el filtro clásico de los sintetizadores analógicos, el pasabajos “resonante”, con su correspondiente método setResonanceEnvelope (en este caso no existe “setResonanceModulator”, aunque de todas maneras se puede usar cualquier señal en lugar de una envolvente). Veámoslo en acción:

Ejemplo #14 – Sinte sustractivo con generador de pulsos modulado y filtro resonante

  // pulse1 contiene el mismo generador de señal del ejemplo #6
  ADSR env4 = new ADSR (5, 800, .5, 100, sr, gate);             // envolvente 4 (de amplitud)
  SignalClone env14 = new SignalClone (env4);                   // clonamos envolvente 4
  SawWave saw = new SawWave (261.63, .95, sr);                  // nueva 'dientes de sierra'
  PulseWave pulse2 = new PulseWave (65.41, .95, sr);            // nuevo generador de pulsos
  saw.setEnvelope (env4);                                       // agregamos la envolvente 4 a la 'dientes de sierra'
  saw.setPan (.8);                                              // paneamos la 'dientes de sierra'
  pulse2.setPan (-.8);                                          // paneamos los pulsos
  pulse2.setEnvelope (env14);                                   // agregamos el clon de la envolvente 4 a los pulsos
  ADSR env5 = new ADSR (1180, 400, .5, 300, sr, gate);          // envolvente 5 (de resonancia)
  SineWave lfo2 = new SineWave (4, .5, sr);                     // nuevo lfo
  ADSR env6 = new ADSR (2700, 10, 1, 300, sr, gate);            // envolvente 6
  lfo2.setEnvelope (env6);                                      // asignamos la envolvente 6 al lfo
  LowPassResonant lp = new LowPassResonant (1000, .7, sr);      // nuevo filtro pasabajos resonante (resonancia=7)
  lp.setFrequencyModulator (lfo2, 80, 5500);                    // asignamos el lfo a la frecuencia de corte del pasabajos
  lp.setResonanceEnvelope (env5);                               // asignamos la envolvente 5 a la resonancia del filtro
  out1.addSignal (saw, Oscillator.RELATIVE);                    // agregamos la 'dientes de sierra' a la salida
  out1.addSignal (pulse2, Oscillator.RELATIVE);                 // agregamos los pulsos la salida
  out1.addSignal (pulse1, Oscillator.RELATIVE);                 // agregamos la onda del ejemplo #6 la salida
  out1.addEffect (lp);                                          // agregamos el filtro resonante a la salida

 

Nuevo efecto: “WaveShaper”

La técnica del waveshaping consiste en procesar la señal con algún tipo de función no lineal, a efectos de obtener espectros complejos, a partir incluso de una simple sinusoide. Parte del interés que esto despierta, proviene de que la complejidad del espectro generado varía según la amplitud de la señal de entrada, lo que guarda cierta semejanza con lo que ocurre en instrumentos acústicos. El waveshaping es, ni más ni menos que, una distorsión.

En el caso de nuestro WaveShaper, la función elegida son los polinomios de chebyshev, calculados recursivamente, cuyo orden se puede especificar al instanciar el efecto. Escuchémoslo:

Ejemplo #15 – Sintetizador FM con filtro resonante y WaveShaper

  ADSR env1 = new ADSR (940, 360, .35, 120, sr, gate);
  ADSR env2 = new ADSR (240, 280, .25, 60, sr, gate);
  ADSR env3 = new ADSR (5, 280, .15, 900, sr, gate);
  SineWave modulator1 = new SineWave (261.63*2.5, .1, sr);
  SineWave modulator2 = new SineWave (261.63*3.5, .2, sr);
  modulator2.setEnvelope (env1);
  modulator1.setEnvelope (env2);
  Adder modulator3 = new Adder();
  modulator3.addSignal (modulator1, Oscillator.RELATIVE);
  modulator3.addSignal (modulator2, Oscillator.RELATIVE);
  WhiteNoise wn = new WhiteNoise (.001);
  SignalClone wn1 = new SignalClone (wn);
  modulator3.addSignal (wn1);
  SineWave sine = new SineWave(261.63, 0.23, sr);
  SignalClone env12 = new SignalClone (env2);
  sine.setEnvelope (env12);
  sine.setFrequencyModulator (modulator3, Oscillator.RELATIVE);
  BandLimitedPulse blp = new BandLimitedPulse (261.63/4, 1.3, 10, sr);
  blp.setEnvelope (env3); sine.setPan(.4); blp.setPan(-.2);
  ADSR adsr4 = new ADSR (10, 1500, .08, 100, sr, gate);
  LowPassResonant lpr = new LowPassResonant (500, .33, sr);
  lpr.setFrequencyEnvelope (adsr4, 100, 8000);
  WaveShaper ws = new WaveShaper (7, 1.1);               // waveshaper de orden 7, amplitud 1.1 (el orden tiene que ser impar)
  out1.addSignal (sine, Adder.RELATIVE);
  out1.addSignal (blp, Adder.RELATIVE);
  out1.addSignal (wn);
  out1.addEffect (lpr);
  out1.addEffect (ws);                                   // agregamos el waveshaper a la salida 

 

Usar el audio de entrada

Una cosa que le faltaba a Minim, era una manera sencilla de insertar la señal de entrada en la cadena del audio de salida. Al hacer que AudioInput implemente AudioSignal, básicamente, logramos que el audio de entrada pueda no sólo escucharse, sino también insertarse como señal de control en todos los puntos antes vistos. AudioInput ya contaba con la posibilidad de agregar efectos, lo que sumado a su nueva funcionalidad, nos abre un interesante abanico de posibilidades.

Para los siguientes ejemplos necesitaremos tener un micrófono conectado a la tarjeta de sonido, y ruteado como señal de “record” desde el mixer de Windows (o su equivalente en OSX o Linux). Lamentablemente, por un tema de seguridad de Java, no es posible disponer del audio de entrada en un applet de internet; por esta razón, los ejemplos 16 y 17 están para bajar como archivos .zip. Los usuarios Windows pueden sencillamente correr el .exe que viene adentro; los usuarios Mac y Linux tienen que correr la clase “ejemplo_xx” dentro del .jar del mismo nombre.

Ejemplo #16 – Audio de entrada con filtro pasabanda modulado y WaveShaper

  AudioInput in = minim.getLineIn (Minim.MONO, 1024, sr, bd);
  SignalClone in1 = new SignalClone (in);
  WhiteNoise wn = new WhiteNoise (.01);
  BandPass bp = new BandPass (500, 300, sr);
  SineWave sine = new SineWave (261.63, .001, sr);
  SineWave lfo = new SineWave (.9, 1, sr);
  SignalClone lfo1 = new SignalClone (lfo);
  bp.setFrequencyModulator (lfo1, 300, 3000);
  bp.setBandWidthModulator (lfo1, 100, 1000);
  WaveShaper ws = new WaveShaper (5, .45);              
  out1.addSignal (sine, Adder.RELATIVE);
  out1.addSignal (lfo, Adder.RELATIVE);
  out1.addSignal (wn);
  out1.addSignal (in);
  out1.addEffect (bp);
  out1.addEffect (ws);

 

Nuevo efecto: “EnvelopeDetect”

Y hablando de procesar el audio de entrada y usarlo como señal de control de un sintetizador, nada mejor que extraer su nivel RMS. Esto es lo que hace EnvelopeDetect, con su único parámetro “decay” que determina la cantidad de muestras a promediar.

Veamos lo que puede conseguirse gracias a este efecto:

Ejemplo #17 – Audio de entrada con EnvelopeDetect, controlando diversas cosas 

  AudioInput in = minim.getLineIn(Minim.MONO, 512, sr, bd);   // definimos una entrada de audio
  EnvelopeDetect ed = new EnvelopeDetect (1500);              // definimos un detector de envolvente;  decay=1500 muestras
  in.addEffect (ed);                                          // agregamos el detector de envolvente a la entrada
  SignalClone in1 = new SignalClone (in);                     // clonamos dicha señal
  SineWave s = new SineWave (10, 0, sr);        
  SawWave saw = new SawWave (130.82, 300, sr);    
  saw.setFrequencyModulator (in);  
  saw.setEnvelope (in1);
  Adder adder0 = new Adder ();
  adder0.addSignal (s, Adder.RELATIVE);
  adder0.addSignal (saw, Adder.RELATIVE);                   
  BandPass bp1 = new BandPass (2000, 200, sr);                      
  bp1.setFrequencyEnvelope (in1, 80, 8000);
  adder0.addEffect (bp1);   
  Adder adder1 = new Adder ();
  adder1.addSignal (adder0, Adder.RELATIVE);
  adder1.addSignal (in1);
  BandLimitedPulse crr = new BandLimitedPulse (261.63, .8, 2, sr);  
  crr.setEnvelope (in1);                                           
  crr.setFrequencyModulator (adder1, Oscillator.RELATIVE);  
  out1.addSignal (crr, Oscillator.RELATIVE); 

  

Conclusión: cosas que faltan

La gran virtud de Minim es el ser lo suficientemente simple, clara (me hace acordar a cierta empresa de telefonía celular) abierta y bien documentada, como para “entrarle” sin tener que estudiar la biblioteca durante meses.

No obstante, incluso con las modificaciones introducidas, el desempeño es limitado, si se lo compara con el de algunos de los productos que mencioné al principio (Reaktor, Max-MSP, SuperCollider, Csound, etc.), los cuales, hay que decirlo también, cuentan en su mayoría con más de 10 años de desarrollo.

Aun así, Minim sigue siendo un excelente punto de partida para seguir trabajando, con miras a disponer algun día, en el entorno Processing, de una herramienta tan poderosa como lo son aquellos productos. Enumero a continuación las funciones que me gustaría agregar en el futuro, haciendo un gran paréntesis con respecto a los temas de fondo: “calidad” de los algoritmos, consumo de recursos, estabilidad, latencia, soportes ASIO y VST, etc., etc.

  • delays, reverbs, chorus, phaser
  • delay variable (modulable)
  • banco de filtros (para hacer un vocoder, por ejemplo)
  • procesadores de dinámica
  • secuenciadores
  • que las envolventes se puedan invertir y retrasar
  • Signal-to-Gate
  • algo parecido al “RELATIVE” para la frecuencia de los filtros
  • y también para los moduladores de amplitud (resultó bueno el RELATIVE ;)
  • a algunas cosas les falta control de nivel
  • sería interesante que en el Adder cada señal tuviera su nivel
  • en las señales de control no debe existir límite de nivel
  • a algunas cosas les falta el soporte estéreo
  • que los ruidos blanco y rosa soporten envolventes de amplitud
  • “macros”: estos serían combinaciones preestablecidas de módulos básicos, encapsulados en una única clase. Por ejemplo: un sintetizador sustractivo completo, o un sintetizador FM configurable, etc., etc.

Para descargar el código fuente de Minim 2.02 PE, ir a la zona de descargas, siguiendo este link.

P. G. 18-01-2010

 

Compártelo:
  • BarraPunto
  • Bitacoras.com
  • email
  • Facebook
  • Google Bookmarks
  • LinkedIn
  • Live
  • MySpace
  • PDF
  • Print
  • RSS
  • Twitter
  • Yahoo! Bookmarks
  • Yahoo! Buzz