sábado, 11 de julio de 2009

De circuitos y códigos

Recientemente estoy liado con la generación de señales de audio. Hay varios sistemas y la mayoría de ellos consisten en traducir un circuito a una serie de instrucciones de código. Los circuitos pueden ser vistos como grafos dirigidos donde cada nodo realiza una serie de tareas obteniendo la información de los arcos que le llegan y devolviendo la información procesada por los arcos que salen de él. Para simplificar las ideas me ceñiré a los grafos dirigidos acíclicos (conocidos por sus siglas en inglés DAG).

Ahora bien, ¿cómo convertir el DAG en una serie de instrucciones? Para empezar (y dado que no tenemos ciclos) podemos ordenar el DAG mediante su orden topológico. Luego tendremos que preguntarnos sobre la naturaleza de las señales que se conectan.
En CSOUND, las señales son o bien arreglos de flotantes o bien números flotantes sueltos. En esta plataforma se combinan ambos conceptos haciendo que un número flotante sea un arreglo de longitud unitaria. Esto hace que la estructura sea muy simple.
  • Cada nodo tiene punteros a los trozos de memoria donde están sus entradas.
  • Cada nodo tiene punteros a los trozos de memoria donde poner sus salidas.
  • Se ejecutan los nodos en el orden topológico y ellos procesan los datos.
Ahora bien, debido a que el nodo no contiene la memoria de esas señales de entrada o salida, el sistema debe preocuparse en reservarlo y hacer que esos punteros apunten donde tienen que apuntar. Otra desventaja es que no hay control de flujo. Siempre hay que procesar las señales, aunque estas no hayan sido modificadas.
En Reaktor se soluciona esta desventaja a costa de tener que calcular el orden topológico cada vez que se quiera modificar o leer una señal. La estructura es bastante más compleja.
  • Los nodos no saben por donde les llegan las señales, pero deben responder a los eventos que las modifiquen (venga de donde venga)
  • Los nodos no saben dónde hay que enviar los resultados de su procesamiento. Es decir, que deben almacenarlo internamente hasta que se lo pidan.
  • Los nodos se ejecutan bajo demanda mediante una búsqueda en profundidad. Esta búsqueda es equivalente a calcular el orden topológico aunque sólo para los nodos que lo requieran.
  • Un nodo que quiera modificar un evento ha de comunicárselo al sistema y el sistema ya decidirá qué hacer con el evento.
  • Un nodo que requiera datos para procesar ha de comunicárselo al sistema y el sistema ya decidirá de dónde sacarlos.
Como se observa, el sistema tiene muchas más responsabilidades en este segundo caso. Lo que significa que va a tener más estructuras auxiliares y va a usar algoritmos más complejos para hacer funcionar el circuito. A cambio, el proceso se realiza únicamente cuando se necesita y no continuamente.
Finalmente, se me ocurre otro sistema en el cual cada señal lleva asociado un flag que indica si ha sido modificada o no. Esto sería un punto intermedio entre los extremos anteriores, aunque tiene trampa ya que el nodo debe utilizar estos flags coherentemente volcando parte de la complejidad en los nodos. Esto último no es recomendable puesto que hay muchos nodos mientras que sólo hay un único sistema.
Por ahora, creo que voy a seguir por la senda de Reaktor.

0 comentarios:

Publicar un comentario