Integración Boost.GIL
From Psychosynth
Perdón por escribir semejante tocho y que las dudas estén un poco disperas. Espero no explicarme demasiado "para mí" y que podáis entender algo. La idea es dejar escrito todo el razonamiento para que podáis criticar tanto las decisiones que estoy tomando cómo los por qués.
Como ya comenté anteriormente el primer paso que voy a dar en el proceso de remodelar el Psychosynth como parte del PFC es integrar Boost.GIL [1]. Vamos a discutir un poco el por qué, los antedecentes y el cómo.
Contents |
¿Qué es Boost.GIL?
Boost.GIL es una biblioteca que ha hecho Adobe y que después se ha integrado en Boost para el procesamiento de imágenes. Lo que hace a GIL especial es que permite a uno dar una descripción genérica de un algoritmo de procesamiento de imágenes y, a través de las lindezas de la metaprogramación, GIL se encarga en traducirlo en la mayoría de los casos a código cuasi-óptimo para formatos de imágenes arbitrários (se puede cambiar el espacio de color, la disposición de los colores en memoria, si los canales están entrelazados o es planar, los bits por color, etc etc).
Las ventajas, para procesamiento de imágenes, son evidentes. Además, la biblioteca ha sido diseñada con una gran elegancia matemática. De la misma documentación se nota la influencia de el "metodo deductivo para la programación" que Alexander Stepanov --autor de la STL-- expone en su muy recomendable libro "Elements of Computer Programmíng". No es de extrañar, ya que Stepanov ha trabajado los últimos años en Adobe dónde se ha dedicado a divulgar la programación genérica y "formal".
¿En qué afecta a la programación de software de audio?
Mi idea es que buena parte de los conceptos que utiliza la biblioteca son análogos al procesamiento de audio. Por ejemplo, en lugar de mono, RGB o CYMK, en audio podemos hablar de Mono, Estereo, 2.1 o 5.1.
Prescindiendo de todo el pitoste de 2D que añade Gil (un análogo a los iteradores llamados "locators" o localizadores) se puede adaptar a 1D, cambiar la terminología (por ejemplo, sustituir "pixel" por "frame") y crear algunas estructuras de datos propias sobre ellas (un buffer y un ring_buffer) basadas en los principios y sobre la base de código de GIL.
¿Pero, es GIL necesario? o ¿Cómo se han hecho las cosas hasta ahora?
Lo cierto es que esta aproximación sería novedosa en el procesamiento de audio. Esto añade un riesgo y es por ello que me gustaría que la voz de la experiencia de alguien me susurre si voy bien encaminado o no.
Hasta ahora en casi todos los sintetizadores modulares se simplifica el problema de una forma sencilla: todas las señales de audio son iguales y monocanal. Cuando un módulo emite salida estereo, simplemente expone dos salidas. Cuando un dispositivo de salida es multicanal, expone 4 entradas. Todos esos puertos se representan con algun tipo de tipo buffer que lo más probable es que sea un float*...
Ya en la primera versión de Psychosynth tomamos un camino diferente, existen dos tipos de señales: "señales normales" (equívocamente llamadas "control") y "señales de audio" (llamadas "audio"). Hablando a lo bruto, la diferencia es que una señal control es un float* y una señal audio es float** --el audio es multicanal, el control no. Todos los algoritmos que trabajan con audio escalan al numero de canales. Todas las señales de audio de una misma red tienen el mismo número de canales.
Esto me pareció interesante porque así los algoritmos de procesamiento de audio podían "escalar" al número de canales necesario sin tener que fijarse individualmente a cada uno. Además, soluciona el problema de "agrupamiento de canales" que sistemas como LV2 solucionan ad-hoc. Como inconveniente, añade procesamiento de audio adicional porque hay veces que todos los canales llevan la misma señal. También, a veces produce duplicidad de código en nodos de control y audio que son algorítmicamente similares.
Las preguntas que se me vienen son, ¿añadiría GIL "demasiada" genericidad? ¿Debería hacer las cosas como "de toda la vida" y solucionar los problemas que eso genere después?
¿Qué podría aportar una biblioteca basada en GIL?
GIL podría usarse de diferentes formas. Una de ellas que se me ocurre sería, haciendo también unos puertos de entrada/salida inteligentes basadas en la extensión "dynamic_image" de GIL, acabar con la dicotomía entre señales de audio y control. Cada nodo podría explicitar si su puerto "requiere" o "recomienda" cierto formato de buffer y luego adaptarse al buffer que el sistema le asigne en cada momento dependiendo de la topología de la red. Todo esto se produce de forma transparente.
Un problema que le veo a esto es que GIL en "dinamico" funciona como "mundo cerrado", ya que dynamic_image se basa en gil::variant (muy similar a boost::variant). Esto significa que todos los formatos de bufferes que vaya a aceptar el sistema tienen que estar hardcodeados en alguna parte del programa --aunque se podría controlar con flags de compilación. Hay que tener en cuenta que ese conjunto no se puede hacer demasiado grande, porque todos los algoritmos genéricos que trabajen sobre estos bufferes dinámicos de GIL se instanciarán automáticamente para todos los formatos de este "mundo cerrado" -- si nos colamos, el tiempo de compilación y el tamaño del ejecutable se van a volver extremos.
También GIL podría usarse a la hora de convertir entre formatos de entrada y salida que diferentes bibliotecas (entrelazado/no entrelazado, formato flotante, o entero de 32, 24 u 8, etc...) requieren cosa y permitiría construir adaptadores interesantes.
¿Si la usamos, cómo integrarla?
Ok, supongamos que la usamos, ¿ahora cómo la integramos? Existen tres opciones, con diferentes ventajas e inconvenientes. Vamos a describirlas un poco:
a) No hacer nada. Usar GIL tal cual y añadir las cosas que no vienen --como bufferes circulares-- y lo demás usarlo tal cual.
Ventajas: Poco trabajo. Actualizaciones a upstream automáticas.
Desventajas: La gente se quedará a cuadros ver tipos como "color_base" usadas para describir la configuración de los colores de la señal, etc. Al acceder a la documentación la gente se verá confusa por todas las movidas multidimensionales y referentes al procesamiento de imágenes. Hay que tener en cuenta que Psychosynth no es sólo un programa sino un frame-work y hay que pensar en los que usen en el futuro nuestra plataforma como biblioteca externa y que esperen cierta coherencia.
b) Usar GIL como biblioteca externa y sobre ella construir un envoltorio con los nombres adaptados.
Ventajas: Trabajo medio. Actualizaciones a upstream casi automáticas.
Desventajas: En los errores de compilació a veces aparecería GIL por detrás aportando más confusión de la que los errores con plantillas suelen aportar de por sí. Además, sin los "template aliases" de C++0x [2] puede ser bastante engorroso e incluso imposible envolver algunas plantillas y envolver algunas funciones puede ocasionar que el compilador deje de aplicarles el "inline" adecuadamente.
c) Importar el código en nuestro árbol y modificarlo a gusto.
Ventajas: Nos podemos asegurar de que el resultado se adapte a nuestras necesidades.
Desventajas: Bastante trabajo, revisar unas 15k lineas de código de metaprogramación a tope, borrar, modificar aquí y allá. Además, los cambios en el "upstream" de GIL se vuelven difíciles de integrar.
¿Qué estoy haciendo finalmente?
Lo más importante que estoy haciendo es comerme la cabeza y escribir este correo para ir, por un lado, ordenándome las ideas en el coco y, segundo, esperar que podáis aportar alguna idea interesante ¡o... incluso mandar todo esto a la mierda! :-)
Pero para no andar de brazos cruzados filosofando, he decidido optar por la opción (c) anterior. Por ahora estoy dando una primera pasada de adaptar el código a las convenciones del proyecto (¿es esto una tontería maniática que sólo sirve para dificultar más importar los cambios de upstream?) a la vez que leo el código para entender los entresijos de GIL para prepararme a la siguiente fase: borrar toda la parte de 2D, cambiar nombres de clases y funciones y finalmente añadir las cosas que me faltan.
El trabajo lo estoy realizando en la siguiente rama que podéis descargar con:
bzr branch bzr://bzr.sv.gnu.org/psychosynth/import-gil
En este mirror en SVN podéis verlo sin descargarlo, aunque los números y fechas de las revisiones no aparecen correctamente:
https://forja.rediris.es/plugins/scmsvn/viewcvs.php/gil-import/?root=cusl5-psynth
El código que interesa ahora está en "src/psynth/snd". Se espera que tras la importación de GIL nuestra biblioteca resultante esté en el namespace "psynth::snd" en lugar de "boost::gil". [3]
Perdón por escribir un tocho tan grande para que las dudas queden tan disperasas.
Saludos,
JP
[1] http://www.boost.org/doc/libs/1_44_0/libs/gil/doc/index.html
[2] Esa es otra de las novedades del, proyecto, vamos a empezar a usar
todas las funciones de C++0x según vayan apareciendo en la última
versión de GCC.
[3] Aunque no tengo claro si mejor llamar al módulo "psynth::sound" ya
que me gustan los nombres pronunciables, gusto que contradice con mi
gusto por los nombres relativamente cortos :p

