Aplicación de Música Generativa Modelos de composición musical asistida por computadora
Autores: Tarcisio Lucas Pirotta y Matías Romero Costas Proyecto de investigación “El Arte Generativo en las Artes Multimediales.” – Director Carmelo Saitta, Codirector Emiliano Causa –Área de Artes Multimediales del Instituto Universitario Nacional de Arte – Buenos Aires (Argentina) – Yatay 843 Ciudad Autónoma de Buenos Aires Tel. 54-11-4862-8209
[email protected] /
[email protected]
Palabras claves Arte generativo, aplicación, música generativa composición algorítmica.
1. Introducción El presente documento describe el diseño y desarrollo de dos aplicaciones de arte generativo, como parte del plan de trabajo del proyecto de investigación “El Arte Generativo en las Artes Multimediales” dirigido por Carmelo Saitta. En este informe, se describen todos los procesos realizados para el diseño de las aplicaciones en Processing1 y Pure Data2. Gran parte del trabajo se centró en la experimentación de la librería Fisica3 de Ricard Marxer para Processing. La idea inicial a partir de la cual surgen estas aplicaciones fue la de proponer un espacio lúdico donde el usuario pueda experimentar con formas, colores y sonidos, donde los componentes tuvieran comportamientos determinados y dejar librado a las leyes físicas la interacción de los mismos para producir un resultado generativo. Esta idea general luego tomó forma más concreta al definir la aplicación como un ambiente virtual donde el usuario pudiera crear y manipular partículas, registrar la interacción entre ellas, establecer relaciones y traducirlas en la síntesis de estructuras visuales y sonoras. Como punto de partida se tomó de referencia una serie de algoritmos desarrollados en gran parte para dispositivos móviles, aplicaciones que mayoría se centraban en la generación de patrones sonoros, como SoundBow4, Seaquence5 y Sonaur6, y es a partir de ahí donde surgió la inquietud de realizar aplicaciones que también tuvieran un correlato visual.
2. Diseño y lógica de las Aplicaciones 1
http://processing.org http://puredata.info 3 www.ricardmarxer.com/fisica 4 http://www.creativeapplications.net/android/soundbow-drawing-based-musical-instrument-forandroid/ 5 http://www.creativeapplications.net/flash/seaquence-flash-sound/ 6 http://www.creativeapplications.net/android/sonaur-android-processing/ 2
1
Como se mencionó al inicio, la idea de las aplicaciones fue crear un ambiente donde el usuario pudiera manipular distintas partículas y establecer relaciones que pudieran generar patrones visuales y sonoros. En el caso de la primer aplicación, que llamaremos Circulos y Triangulos, se tomó como parámetro generativo el contacto entre las diferentes partículas, a las que llamaremos células. En relación a la segunda aplicación, que llamaremos Ondas y Líneas se tomó como parámetro la relación de proximidad de las diferentes células. Cada aplicación tiene dos módulos, el módulo de interacción y visualización, y el módulo sonoro, ambos conectados a través del protocolo OSC. El código de las aplicaciones esta disponible para descargar en http://www.biopus.com.ar/arte_generativo.zip
2.1 Módulo de visualización e interacción El algoritmo de este módulo está implementado en processing y utiliza la librería Fisica para la simulación de las propiedades físicas.
2.1.1 Fisica http://www.ricardmarxer.com/fisica/ Librería creada por Ricard Marxer para el entorno de Processing a inicios del 2011. Es un simple wrapper de Jbox2D7 que intenta hacer mucho más fácil la creación de modelos físicos utilizando objetos del motor dentro del mismo Processing y es compatible para plataformas Nintendo DS, Wii, Android y iPhone como también en la mayoría de los sistemas operativos. El sistema de detección y resolución de partículas consta de tres partes: un barrido incremental y corte de fase amplia, una unidad de detección de colisiones continua y generador de contacto en tiempo continuo. Estos algoritmos permiten eficientes simulaciones rápidas y con grandes pilas sin perder colisiones o causar inestabilidad. Dentro de las capacidades del motor, podemos mencionar las siguientes: Colisiones: - detección continua de colisiones - detección simultánea de múltiples contactos - círculos y polígonos convexos Física: - gravedad - contacto, fricción, restitución - corrección de posición desacoplada - precisión de fuerzas de reacción e impulso La librería tiene tres clases principales que son las que se han utilizado en las aplicaciones desarrolladas: FWorld, FBody y FContact.
2.1.1.1 FWorld Define un objeto que simula el mundo y que controla todos los elementos dentro de la simulación definida. Este almacena un listado de todos los objetos presentes, así como todos sus puntos de conexión. Los principales métodos de la clase son: - setEdges() Define los límites del mundo, si no es especifican los valores,toma las dimensiones del escenario.
7
Es una versión para Java del motor Box2D creado por Erin Catto. http://www.jbox2d.org/
2
- setGrabbable(boolean valor) Determina si los cuerpos del mundo pueden o no (true o false) ser controlados con el mouse. - setGravity(float valorx, float valory) Define los valores de gravedad, tanto en el eje vertical como horizontal. - add(FBody cuerpo) / remove(FBody cuerpo) Agregan o quitan, respectivamente, un objeto al mundo. - step() Avanza un paso de 1/60 segundos en la simulación del mundo, es lo que digamos que pone el mundo en marcha. Este método debe ser ejecutado continuamente. - draw() Dibuja todos los objetos de la simulación. También debe ser ejecutado continuamente.
2.1.1.2 FBody Define un objeto que simula un cuerpo en el mundo. Este cuerpo puede reaccionar contra otros cuerpos y tiene propiedades como densidad, posición, velocidad, rotación. Esta clase tiene a su vez cinco subclases FBlob, FBox, FCircle, FLine y FPoly que definen objetos con forma de manchas, cuadrados, círculos, líneas o polígonos respectivamente. Los principales métodos de FBody, aplicables a todas sus subclases, son: setName(String name) Define el nombre del cuerpo para luego poder identificarlo setFill(float r, float g, float b, float a) Define el color de relleno en rgba. setNoFill() Determina que el cuerpo no tendrá relleno. setStroke(float r, float g, float b, float a) Define el color de la línea en rgba. setNoStrokel() Determina que el cuerpo no tendrá línea. setPosition(float x, float y) Define la posición del cuerpo. setRotation(float w) define la rotación del cuerpo. setFriction() define la rotación del cuerpo. setRestitution() Define el valor de rebote del cuerpo. setVelocity(float vx, float vy) Define la velocidad inicial del cuerpo, tanto en el eje horizontal como vertical. getName() Devuelve el nombre del cuerpo. getY() / getY() Devuelven los valores de posición en el eje horizontal y vertical respectivamente. getVelocityX() / getVelocityY() Devuelven los valores de velocidad en el eje horizontal y vertical respectivamente.
2.1.1.3 FContact Representa un contacto entre dos cuerpos. Este tipo de objetos no son definidos por el usuario, sino que se inicializan automáticamente cuando hay un contacto a través de la ejecución de la función contactStarted(FContact) Podríamos definirlo como un evento, que devuelve distintos valores, asociados principalmente a los cuerpos internvinientes en el contacto. Sus principales métodos son: getBody1() / getBody1() devuelve respectivamente el primer y segundo cuerpo involucrado en el contacto getFriction() devuelve la fricción de impacto getX() / getX() devuelve la posición en X e Y del contacto. getVelocityX() / getVelocityY() devuelve la velocidad en X e Y del contacto.
Habiendo descrito las principales clases y métodos de la librería, a continuación detallaremos la implementación concreta en cada una de las aplicaciones.
2.1.2 Circulos y Triangulos (aplicación 1) Esta es una aplicación en la que el usuario puede generar y manipular células a través de la interacción con el mouse, el parámetro generativo es el contacto entre ellas. Los cuerpos generados 3
son de dos clases, dos especies de células: circulos y triangulos. Ver figuras 1, 2 y 3.
Figura 1: Estado inicial de la aplicación. El usuario ha agregado 3 células.
Figura 2: Variedad de células, se aprecia la predominancia de los triángulos
4
Figura 3: células en movimiento.
Los círculos tienen un carácter viral, en contacto con células de otra especie, las “contagia” agregando lados en sucesivos contactos o rebotes, haciendo que que la “víctima” se vuelva cada vez mas circulo. Ver figura 4. Este proceso va modificando internamente el estadio de la celula triángulo (variable asociada a la cantidad de lados y que representa el estado de transformación triangulocirculo). Este proceso se repite hasta un extremo en el cual la célula es transformada plenamente en un círculo y cambia su identidad de especie. Ver figura 5.
Figura 4: Evolución primaria del triangulo, vemos la transformación desde un estadio inicial 3 en la derecha hasta 7 en la izquierda.
Figura 5: Predominio de circulos, varios triangulos han sido transformados
Para la implementación de lo anteriormente descrito, se diseñó la clase Celula, que será el componente principal de la aplicación:
Celula( float x_ , float y_,float radio_ , int index_){ iniciar( x_ , y_,radio_ , index_); } void iniciar( float origenX_ , float origenY_,float radio_, int index_ ){ origenX = origenX_; origenY = origenY_; radio=radio_; index=index_; if (random(100) >75){
5
especie=0;//si es circulo estadio=30; }else{ especie=1;//1 si es triangulo estadio=3; } colorCelula=agua_fuego.seleccionaColor(estadio); sonidoCelula=int(random(12)); rebotes=0; crearCelula(); }
Como vemos en el código, la fnción iniciar(), que recibe en su constructor cuatro parámetros, x, y, radio e index, configura el objeto definiendo una serie de variables relacionadas con la posición, el color, los rebotes y el sonido. Sin embargo las más importantes son las variables especie y estadio, que hacen diferenciar un tipo de célula de otra, son las que definen el carácter formal de la célula. A su vez el código está diseñado para que haya una mayor probabilidad de células triángulos. Con respecto al color de la célula, la variable de colorCelula toma su valor utilizando el método seleccionaColor() de la clase Paleta. El color es elegido aleatoriamente dentro de un rango determinado acorde a cada estadio, como vemos en la figura 6. Esto es así para que todas los cuerpos que se encuentran en un mismo estadio compartan una tonalidad, pero no tengan exactamente el mismo color. Las tonalidades frías, asociadas al agua o hielo, pertenecen a los triángulos y las cálidas, asociadas al fuego, a los círculos.
Figura 6: Estructura cromatica Finalmente en la ultima linea del código de niciar() obervamos la función crearCelula(): void crearCelula(){ celulaPoly = new FPoly(); Punto pcentro = new Punto(origenX,origenY); Punto[] vertices; vertices= new Punto[int(estadio)]; for (int i=0;i ( factorProximidad+otraCelula.factorProximidad)*0.7 )&&( distancia < (factorProximidad+otraCelula.factorProximidad)*1.5 )) { pg2.beginShape(); pg2.vertex(celulaPoly.getX(), celulaPoly.getY()); pg2.bezierVertex(celulaPoly.getX()-distx/2,celulaPoly.getY()-(disty/ 2)*2.5,celulaPoly.getX()-distx/2,celulaPoly.getY()-(disty/2)*2.5, otraCelula.celulaPoly.getX(), otraCelula.celulaPoly.getY()); pg2.endShape(); if( (conectadaLinea==false) && (otraCelula.conectadaLinea==false)){ enviaOSC(sonidoCelula, 1 ,int(distancia/10)); enviaOSC(sonidoCelula, 0 ,int(distancia/10)); } conectadaLinea=true; otraCelula.conectadaLinea=true; }else { conectadaLinea=false; otraCelula.conectadaLinea=false; } if ( distancia ( factorProximidad+otraCelula.factorProximidad)*0.7 )&&( distancia < (factorProximidad+otraCelula.factorProximidad)*1.5 )) { pg2.beginShape(); pg2.vertex(celulaPoly.getX(), celulaPoly.getY()); pg2.bezierVertex(celulaPoly.getX()-distx/2,celulaPoly.getY()-(disty/ 2)*2.5,celulaPoly.getX()-distx/2,celulaPoly.getY()-(disty/2)*2.5, otraCelula.celulaPoly.getX(), otraCelula.celulaPoly.getY()); pg2.endShape(); if( (conectadaLinea==false) && (otraCelula.conectadaLinea==false)){ enviaOSC(sonidoCelula, 1 ,int(distancia/10)); enviaOSC(sonidoCelula, 0 ,int(distancia/10)); } conectadaLinea=true; otraCelula.conectadaLinea=true; }else { conectadaLinea=false; otraCelula.conectadaLinea=false; }
En ese caso si se cumple la condición dibuja una línea curva entre las dos células y define el sonido a ejecutar. Cada célula tiene un sonido, una nota determinada aleatoriamente al iniciar el objeto. Luego ese sonido es enviado con distintas tonicidades asociadas al calor de distancia. Los datos son enviados via OSC hacia el módulo desarrollado en PD.
15
Finalmente la tercer parte del código es similar a la anterior, solo que consulta si la distancia esta entre 0 y 0.7: if ( distancia Datos numéricos simbólicos y de alta resolución --> Lenguaje de coincidencia de patrones (pattern matching) para especificar múltiples receptores de un único mensaje --> Marcas de tiempo (time tags) de alta resolución 25
--> Mensajes “empaquetados” para aquellos eventos que deben ocurrir simultáneamente --> Sistema de interrogación para encontrar dinámicamente las capacidades de un servidor OSC y obtener documentación Actualmente varios lenguajes de programación y aplicaciones manejan el protocolo OSC. Esto permite comunicar y compartir datos entre ellos. --> Pure Data --> Max MSP --> Processing --> OpenFrameworks --> Max MSP --> Supercollider --> Flash La base de la comunicación de OSC son los mensajes, que pueden dividirse en dos grupos: --> mensaje único --> paquete de mensajes Cada mensaje está conformado por tres componentes: dirección -- tipos de datos -- datos Ejemplo: /sonido/analisis/espectro , s i f sonido.wav 440 0.9 La primera parte de un mensaje OSC es la dirección (address) y consiste en una cadena de caracteres que inicia con el símbolo “/”. Esto permite definir una estructura jerárquica (de tipo árbol). La dirección se utiliza como una etiqueta para identificar el mensaje. La segunda parte de un mensaje OSC es una cadena que empieza con el símbolo “,” y que está compuesta por letras que sirven para identificar los tipo de datos que el mensaje transporta. Cada letra representa un tipo de dato (cadenas s, números enteros i, números con coma flotante f, etc.) La tercera parte del mensaje son los datos, uno después de otro y sin ningún byte de separación entre ellos. Los números generalmente ocupan 4 bytes (con alguna excepción de 8 byte) mientras las cadenas de caracteres y las matrices de bytes pueden tener un largo variable pero siempre un valor múltiplo de 4.
26
El objeto dumpOSC permite recibir los mensajes por el puerto especificado en su argumento. En la aplicación de origen el puerto de envío debe coincidir con este. Todos los mensajes y paquetes de datos pasan a través de ese objeto. Para desarmar la estructura de árbol se utiliza el objeto OSCroute. Cada uno de los mensajes contiene 2 datos, el que se corresponde con el disparo o pulso s pulso1, y el que determina el grado de tonicidad de cada uno de los 12 instrumentos, s H1. Ambos datos los recibe el sampler.
3 Conclusiones El presente trabajo refleja los alcances logrados en la investigación sobre arte generativo. Avances que fueron plasmados a través de un conjunto de aplicaciones que exploran las posibilidades gráficas y sonoras generativas dentro de un entorno interactivo. En este sentido ambos lenguajes funcionan como refuerzo y síntesis de un funcionamiento sistémico de los componentes visuales y sonoros. Las aplicaciones ponen de manifiesto un proceso generativo, que conjugan la autonomía individual de cada elemento del conjunto con la intervención externa del usuario, y de la cual emerge una comportamiento complejo. Como trabajo a futuro se pretende implementar estas aplicaciones para dispositivos móviles multitouch. Es decir desarrollar una aplicación ejecutable unificada que no necesitará de terceros programas como PD o protocolos de comunicación como OSC. 27
Parte de las tareas realizadas en el presente trabajo consistió investigar las alternativas para poder a partir de Processing generar un producto exportable para dispositivos móviles. Podemos mencionar que hemos estado trabajando con LibPD y Puredatap5, librerías que permiten ejecutar directamente dentro de processing los módulos de sonido de PD. Este trabajo está aún en una etapa preliminar, aunque los avances alcanzados hacen vislumbrar el potencial de estas plataformas.
4 Bibliografía Processing http://processing.org/ Libreria Libpd https://github.com/libpd Puredatap5 https://github.com/libpd/puredatap5 Fisica http://www.ricardmarxer.com/fisica/ BANDWIDTH http://jtnimoy.net/?q=184 ORPHION http://www.creativeapplications.net/sound/orphion-ipad-sound/ http://www.orphion.de/ QDD http://www.creativeapplications.net/games/qdd-iphone-sound-games/ http://www.quasidubdevelopment.com/ CIRCADIA http://www.creativeapplications.net/iphone/circadia-by-kurt-bieg-a-constellation-of-musical-coloursseeking-a-rhythm-ios/ http://www.imore.com/circadia-iphone-ipad SONA http://www.creativeapplications.net/sound/sona-by-ruslan-gaynutdinov-a-game-of-sound-networksecal/
28