Sintaxis básica en SuperCollider
Introducción a la sintaxis de SuperCollider. Manejo elemental de variables, valores numéricos, listas y funciones.
La base para introducirse en SuperCollider es conocer su sintaxis. La diversidad, riqueza y polimorfismo de este lenguaje lo pueden hacer intimidante para quienes se acercan a él por primera vez. Este texto propone una serie de ejemplos fundamentales para comprender la lógica interna del programa y captar su potencialidad.
En los siguientes ejemplos aún no hay sonido, sino solo fragmentos mínimos de código que muestran el manejo de estructuras. Entender bien estos conceptos sintácticos fundamentales nos permitirá muy pronto sintetizar sonidos y partituras algorítmicas virtualmente sin límites, siempre que sepamos expresar nuestras ideas en este nuevo idioma.
SuperCollider combina dos paradigmas: la programación orientada a objetos y la programación funcional. Sin entrar en detalles, por ahora solo diremos que esos dos elementos, Object y Function, permiten construir estructuras musicales y sonoras de gran complejidad de manera extremadamente concisa.
objetos y variables
Un objeto es simplemente una entidad cuyo comportamiento está bien definido. En SuperCollider, todo es un objeto de alguna clase (Class), desde valores simples a todo tipo de estructuras complejas como listas, matrices, audio, gráficos, funciones, etc.
Una variable es una palabra con la que representaremos cualquier objeto.
En SuperCollider los nombres de variables globales pueden ser letras minúsculas
sueltas, pero hay que evitar usar la s, ya que representa al servidor de audio. Los nombres
de variables más largos y descriptivos empiezan con virgulilla ∼ seguida obligatoriamente por una letra minúscula.
Una vez declaradas, las variables globales son accesibles en cualquier punto posterior del código de un programa.
Un elemento igualmente importante son los comentarios (comments). Un comentario es un texto dentro del código que es ignorado por el intérprete, pero que es esencial para clarificar lo que sucede dentro, tanto para quien programa como para quien lo vaya a leer después. Como en otros lenguajes, en SuperCollider se usa // para comentar una línea, junto con el par /* y */ para comentar un bloque de varias líneas:
aritmética
Empecemos por ejecutar expresiones aritméticas puras, en modo calculadora. Los espacios en blanco entre símbolos no son obligatorios pero sí muy recomendables para facilitar la lectura del código.
SuperCollider opera de izquierda a derecha, sin seguir las reglas matemáticas de precedencia. Los paréntesis tienen múltiples usos; entre ellos, determinar qué operaciones se calculan primero:
Como veremos, existen muchas expresiones abreviadas. Por ejemplo, podemos calcular potencias con un doble asterisco **:
El módulo es una operación no muy conocida aunque extremadamente útil para la composición algorítmica. Puede entenderse como pedir el resto de una división. Como en muchos lenguajes de programación, la representa el símbolo %.
listas
Al crear música necesitamos continuamente listas de elementos (alturas, duraciones, dinámicas, etc.). En SuperCollider hay muchas clases diferentes para representar colecciones de elementos. La más común es el Array. Una de las maneras más sencillas de construirlos es empleando corchetes []:
Se puede operar con los Arrays en bloque:
Un Array puede agrupar elementos de clases diferentes.
Si operamos con dos arrays los elementos se agrupan de esta manera: si la longitud de los Arrays es igual, operando de uno a uno, en caso contrario, el Array más corto se itera en loop, repitiendo sus elementos:
En SuperCollider hay mucho sugar syntax (maneras breves e intuitivas equivalentes a expresiones más complejas). Con el doble punto .. podemos generar Arrays que contengan sucesiones de esta manera tan conveniente:
métodos
Por método (Method) nos referimos a acciones que pueden aplicarse a determinadas clases. Recogiendo un símil típico, podríamos imaginar un objeto de la clase Bicicleta cuyos métodos sean pedalear, girarManillar, frenar y encenderFaro.
métodos sobre números
Tanto enteros como decimales son objetos de la clase Number. Se les puede aplicar numerosísimos métodos. Veamos algunos:
En esta variante sintáctica, el método comienza con un punto y se escribe a continuación del objeto al que se aplica. Ciertos métodos requieren algún valor para operar:
Hay métodos muy convenientes para trabajar con alturas:
De entre los generadores de números aleatorios, .rand y .rrand son los más comunes:
Forma sintáctica alternativa para escribir expresiones equivalentes a las anteriores, más propia de la programación funcional:
métodos sobre listas
Los métodos para un solo número pueden aplicarse también a los Arrays:
Pero hay métodos que solo tienen sentido aplicados a una lista:
Tenemos que emplear variables para almacenar Arrays y poder operar sobre ellos posteriormente. Esto simplifica el código y lo hace mucho más legible. Usando corchetes nos referimos a uno o más elementos del Array.
Hay que tener cuidado dado que ciertos métodos alteran el contenido del Array al que se refiere la variable:
Un método puede tomar un objeto de una clase para dar como salida un objeto de otra clase. El método .dup, muy utilizado, crea duplicados de un objeto de cualquier clase y los agrupa en un Array:
Para esto también hay algo de sugar syntax, utilizando el símbolo !. Versiones abreviadas de las expresiones anteriores:
serialismo con variables, listas y métodos
Combinando los conceptos anteriores y la capacidad expresiva del lenguaje, planteemos ahora un ejemplo más musical. Será un sistema simple para generar series dodecafónicas aleatorias y manipularlas.
Comenzamos creando la variable global de nombre largo ∼serie para almacenar un Array creado aleatoriamente. La serie de alturas estará representada por sus valores en notación Pitch Class, que asigna números a cada nota de la escala cromática (0 = do, 1 = do♯, etc.).
Existen métodos directos para aplicar las consabidas transformaciones habituales de inversión y retrogradación:
Para la transposición se puede aplicar simplemente una suma a la lista de alturas. La expresión % 12, con el operador módulo, permite resituar los números mayores que 11 en sus valores correspondientes de Pitch Class.
Combinando varios métodos y operaciones aritméticas anteriores, construimos ahora un Array de Arrays que contenga las 12 transposiciones de la serie aleatoria original. La expresión resultante es concisa pero también críptica:
El método .plot representa gráficamente las doce series:
.plot aplicado a un Array de Arrays crea una serie apilada de gráficas.Ahora es trivial manipular cualquier serie derivada de la original:
Combinando métodos y operaciones podemos obtener las frecuencias en Hz de las notas de una serie en cualquier octava:
introducción a las funciones
Otra construcción sintáctica fundamental son las funciones. Se declaran entre llaves { } y son el verdadero corazón operativo del lenguaje donde explota la potencialidad expresiva de SuperCollider. Las funciones merecen capítulo aparte, por lo que por ahora solo introduciremos su sintaxis básica.
Podemos entender una función (Function) como una caja negra que toma elementos de entrada, ejecuta un procedimiento y retorna algo como salida:
Las variables también pueden representar funciones:
Al usar el método .dup es imporante distinguir bien entre lo que implica duplicar un elemento y duplicar una función:
La flexibilidad de las funciones reside en el trabajo con parámetros de entrada, denominados arguments y declarados con la palabra clave arg:
Escribimos ahora una función en varias líneas, que es lo habitual. Otro cometido de los paréntesis es enmarcar un bloque de código para facilitar su evaluación: si el cursor está dentro de los paréntesis, se ejecutan todas las líneas internas sin tener que seleccionarlas expresamente. El punto y coma ; es obligatorio para separar expresiones, usualmente al final de cada línea; de lo contrario obtendremos un error.
En la función anterior hay que distinguir entre arg, que declara los parámetros de entrada (arguments), y var, que declara variables locales no accesibles desde fuera, cuya misión es ayudar a organizar y clarificar el código interno de la función.
Al emplear esta nueva función, hay varias formas de especificar los valores de entrada para los argumentos frecA y frecB:
Hemos sacado partido aquí de varias reglas para abreviar el código:
- Si no se especifica la etiqueta de un argumento, simplemente se asigna siguiendo su orden interno.
- Sustituimos la llamada al método
.valuepor apenas un punto.
métodos que usan funciones
Para terminar, un concepto más avanzado: métodos que requieren funciones como argumento. Podemos perfeccionar nuestro algoritmo serial anterior definiendo una función que nos permita traducir los números a nombre de notas, haciendo así mucho más legibles los resultados.
Primero creamos un Array de Strings con los nombres de todas las notas, de modo que su lugar en el Array se corresponda convenientemente con su equivalente en Pitch Class:
El siguiente paso es definir la función que convierta un número en el nombre de la nota:
Ahora podemos emplear nuestra nueva función ∼traduceNota con el método .collect, el cual devuelve un Array tras aplicar una función a cada elemento de un Array inicial:
Finalmente, aplicamos esta construcción (un método que toma una función) a ~tablaTranspos, calculando transformaciones de la 7ª transposición y mostrando los resultados como un Arrays con los nombres de cada nota.
* * *
Una versión condensada en un solo archivo con todos estos ejemplos y explicaciones, en el formato propio de SuperCollider, puede descargarse en el siguiente enlace: