Updated:
Plugin LADSPA básico
Requisitos previos
- Un ordenador con Windows/Linux/Mac
- En este artículo usamos C y GNU GCC (lee GCC para Windows). Si quieres también puedes escribirlo en Pascal y usar FPC u otros lenguajes compilados
- Opcionalmente una tarjeta de sonido, un micrófono y un altavoz para probar el plugin en tiempo real usando RTLADSPA
- Tener claros algunos conceptos básicos sobre señales y sistemas (lee más adelante).
Escribir un algoritmo DSP tiene dos tipos diferentes de complejidad:
- La complejidad de la programación en sí, es decir, escribir un algoritmo "ABC" en el lenguaje de programación "XYZ", crear una biblioteca *.DLL, etc
- La complejidad de las matemáticas que hay detrás de un efecto digital de sonido. Recuerda que la programación es sólo una herramienta para resolver un problema. Aquí el problema principal no es la programación, sino el el procesamiento digital de la señal.
Este artículo intenta ayudarte con el primer aspecto. Con respecto a lo segundo quiero decirte que, aunque no es necesario ser un experto en el tema, ni mucho menos, lógicamente debes tener claros algunos conceptos básicos<ref>No se trata de material "avanzado", está al alcance de cualquier autodidacta con un nivel en matemáticas de bachiller</ref> como: señal continua y discreta, sistema, diagrama de bloques, espectro de una señal, muestreo y convertidor A/D y D/A...
Si no tienes claros estos conceptos y quieres saberlos de verdad, léelos en un libro. No es posible explicar esto en 4 párrafos y dejar el asunto zanjado. El tema tiene su miga y requiere que lo estudies con calma. Hay también mucha información en Internet.
El libro gordo de Petete (1000 páginas) sobre el tema es
"Tratamiento digital de señales. Principios, algoritmos y aplicaciones", John G. Proakis, Dimitris G. Manolakis.
Prentice Hall. ISBN
0-13-373762-4
Sobre el formato LADSPA
El formato LADSPA es el más popular entre los programas de audio para Linux y cada vez más en Windows. Otro formato muy conocido es VST de la empresa Steinberg y lo usan programas como Cubase o Audition. He elegido escribir sobre LADSPA y no sobre VST por varias razones:
- LADSPA es mucho más simple de VST
- La especificación es libre, no hay que pagar nada ni por el documento ni por el SDK (en este caso un simple fichero de cabecera: ladspa.h)
- Es posible escribir el plugin fácilmente en cualquier lenguaje compilado (C, Pascal, C++, D, etc) y no obligatoriamente en C/C++ como VST
Un plugin LADSPA no es más que una biblioteca dinámica (fichero *.so en UNIX o *.dll en Windows) y se genera igual que cualquier otra biblioteca. La norma LADSPA especifica qué funciones exportadas debe contener la biblioteca y en qué orden deben ser llamadas por el host (el programa que usa el plugin), las estructuras de datos usadas, etc
La función ladspa_descriptor
Toda biblioteca contiene funciones, algunas de las cuales son públicas (exportadas). Una función exportada es
aquella que puede ser llamada por el host invocando su nombre<ref>Me refiero a llamarlas por su
nombre y no a llamarlas través de un puntero. Una función puede ser llamada a través de puntero siempre,
independientemente de que sea pública o no</ref>. Un plugin LADSPA tan sólo debe exportar una función
que debe llamarse obligatoriamente ladspa_descriptor
y cuya declaración aparece en ladspa.h
EXPORT const LADSPA_Descriptor* ladspa_descriptor(unsigned long Index);
Esta función devuelve un puntero a una estructura de datos de tipo LADSPA_Descriptor
<ref>Se
llama igual que la función, pero se escribe de forma diferente, los identificadores en C escritos en mayúsculas
y minúsculas son diferentes</ref>, que contiene toda la información necesaria para usar el plugin, es por
tanto una estructura muy importante.
EXPORT es una macro que indica que la función es pública. La definición de este macro depende de si estamos en
UNIX o en Windows. El fichero ladspa.h
que yo te ofrezco no es igual al original que puedes
descargar de www.ladspa.org porque lo he adaptado para poder compilar plugins tanto en
UNIX como en Windows. Si usas Windows debes usar el mio ;-)
Puertos de señal y de control
Un plugin LADSPA contiene uno o más bloques de procesado de señal<ref>Llamo "bloque" a un sistema de procesado de señal genérico, p. ej: un filtro o un conjunto de filtros</ref>. Cada instancia de un bloque (ver siguiente apartado) tiene puertos, que son búferes que almacenan muestras de señal y parámetros de control del bloque. La reserva y el tamaño de estos búferes lo fija el host, no el plugin. Los puertos pueden ser de 4 tipos como muestra la figura:
- Puerto de señal de entrada (puertos X,
LADSPA_PORT_AUDIO | LADSPA_PORT_INPUT
). Por donde entran las muestras de la/s señal/es de entrada a una tasa Fs (frecuencia de muestreo). La Fs la define el host.
- Puerto de señal de salida (puertos Y,
LADSPA_PORT_AUDIO | LADSPA_PORT_OUTPUT
). Idem para la/s señal/es de salida.
- Puerto de control de entrada (puertos C,
LADSPA_PORT_CONTROL | LADSPA_PORT_INPUT
). Sirven para que el bloque reciba parámetros de control que varíen su funcionamiento, p. ej: los coeficientes de un filtro lineal.
- Puerto de control de salida (puertos D,
LADSPA_PORT_CONTROL | LADSPA_PORT_OUTPUT
). Sirven fundamentalmente para monitorizar el funcionamiento interno del bloque, p. ej: ver la evolución de los coeficientes de un filtro adaptativo.
Los tipos de datos LADSPA_Descriptor e Instance_data
Un plugin puede contener una o más clases de bloques independientes (lo normal es que sólo tenga uno) teniendo cada uno un número asociado, los números van seguidos y empiezan en 0. Por ejemplo: dentro del plugin "myplugin.dll" puede haber varias clases de bloques, en principio, independientes:
- un filtro FIR (
index = 0
) - un filtro IIR (
index = 1
) - un compansor de ley A (
index = 2
) - etc
Cuando digo "clase de bloque" me refiero a que es posible instanciar varios bloques de la "clase X" (igual que en un lenguaje OO)
- A cada clase de bloque le corresponde una estructura
LADSPA_Descriptor
, que es única para todas las instancias de esa clase - A cada instancia de bloque le corresponde, una estructura de datos llamada
Instance_data
definida por ti, que contiene los datos del bloque, i.e, los puertos del bloque y las variables y búferes de señal necesarios para su funcionamiento.
La siguiente tabla muestra y explica cada uno de los campos de la estructura LADSPA_Descriptor
. Lee
el fichero ladspa.h
para más información sobre los mismos y para ver la definición de las
constantes y de otras estructuras usadas. No es mi intención reproducir en este artículo absolutamente todo lo
que aparece en ese fichero sino dar una idea general, el fichero lo puedes leer por ti mismo ;-)
Campo | Descripcción |
---|---|
UniqueID | Identificador numérico. Sirve para identificar unívocamente el plugin dentro de una colección de ellos. |
Label | Nombre corto del plugin, p. ej: "alienfuzz" |
Name | Nombre completo del plugin, p. ej: "Efecto Alien Borroso" |
Maker | Autor, p. ej: "Perico de los Palotes" |
Copyright | Licencia. p. ej: "GPL" |
Properties | Propiedades del plugin, p. ej: si puede trabajar en tiempo real (LADSPA_PROPERTY_HARD_RT_CAPABLE), etc |
PortCount | Número de puertos en total, contando los de señal y los de control |
PortDescriptors | Array de elementos de tipo LADSPA_PortDescriptor . Cada uno de estos elementos es una
estrucutra de datos que contiene infomación de 1 puerto: si es de señal o control, si de entrada o
salida.
|
PortNames | Array de cadenas (tipo const char ** ) con los nombres de todos los puertos
|
PortRangeHints | Array de elementos de tipo LADSPA_PortRangeHint . Cada uno de estos elementos es una
estrucutra de datos que contiene infomación sobre el rango de valores que admite el puerto.
|
ImplementationData | No se suele usar (leer ladspa.h )
|
instantiate | Puntero a la función instantiate del plugin
|
connect_port | Puntero a la función connect_port del plugin
|
activate | Puntero a la función activate del plugin
|
run | Puntero a la función run del plugin
|
run_adding | Puntero a la función run_adding del plugin
|
set_run_adding_gain | Puntero a la función set_run_adding_gain del plugin
|
deactivate | Puntero a la función deactivate del plugin
|
cleanup | Puntero a la función cleanup del plugin
|
Interfaz entre el host y el plugin. Modelo procesal
En la figura de la derecha puedes ver la secuencia de llamadas que hace el host a funciones del plugin. Excepto
la llamada a la función ladspa_descriptor
, todas las demás las hace a través de los punteros
contenidos en la estructura LADSPA_Descriptor
ladspa_descriptor
El host llama a ladspa_descriptor(unsigned long index)
, que le devuelve un puntero a la estructura
LADSPA_Descriptor
correspondiente a la clase de bloque número index
. Si sólo hay un
bloque, es el 0. Si index
no corresponde a ningún bloque la función ladspa_descriptor
devuelve un puntero nulo (NULL
)
Para cada uno de las clases de bloques contenidas en el plugin, i.e, para cada estructuta
LADSPA_Descriptor
obtenida, la secuencia de llamadas que hace el host es la siguiente...
instantiate
Crea una instancia de bloque. Para ello reserva la memoria necesaria para los búferes de señal y variables
necesarias para el funcionamiento del bloque. Todas las variables deben resistir dentro de una estructuta de
tipo Instance_data
.
activate
Inicializa los datos (Instance_data
) del bloque
connect
El host llama a connect_ports
para asignar los punteros a búfer de señal/control (los puertos).
Además, esta función puede ser llamada en cualquier momento y más de una vez durante el procesado para poder
reasignar los puertos en cualquier momento si fuese necesario.
run
El host llama a run
o bien run_adding
o set_run_adding_gain
una y otra vez
para procesar la señal de entrada hasta que el host decida parar.
deactivate
Lo contrario de activate
. Este función no tiene mucha utilidad y la mayoría de las veces en ella no
se hace nada.
cleanup
Lo contrario de instantiate
. Libera la memoria reservada para los datos del bloque.
Inicializar/Finalizar una biblioteca dinámica
Normalmente necesitamos ejecutar código de inicialización en cuanto el plugin se cargue en memoria para reservar
memoria e inicializar las estructuras LADSPA_Descriptor
(u otras cosas) y código de finalización
cuando la biblioteca se descargue para liberar la memoria reservada, etc. ¿Es posible hacer eso? Sí.
Todas las bibliotecas dinámicas pueden tener funciones de inicialización y de finalización, la forma en que se hace esto difiere en UNIX y en Windows
La siguiente información en realidad no tiene mucho que ver con LADSPA en particular, sino que es información general sobre bibliotecas dinámicas en UNIX y Windows.
EN UNIX
En UNIX puedes indicarle a GCC cuáles son esas funciones mediante los siguientes atributos de función
void __attribute__ ((constructor)) init() { //... } void __attribute__ ((destructor)) fini() { //... }
He elegido los nombres init
y fini
, pero podría haber sido otros, eso no importa. Los
atributos de función constructor
y destructor
le dicen a GCC que cuando genere la
biblioteca, en la cabecera de la misma (fichero objeto con formato ELF) se indique que las funciones de
inicialización/finalización son precisamente estas, de modo que el SO pueda llamarlas automáticamente durante la
carga/descarga de la biblioteca.
Los atributos de función no forman parte de la especificación del lenguaje C/C++ sino que es algo que definen los compiladores, la sintaxis depende por tanto del compilador que uses.
EN WINDOWS
En Windows, se ejecuta automáticamente la función DllMain
(debe llamarse así) cuya declaración es
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { switch( ul_reason_for_call ) { case DLL_PROCESS_ATTACH: // Initializatión break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: // Termination break; } return TRUE; }
La variable ul_reason_for_call
nos indica el porqué la función es llamada:
- DLL_PROCESS_ATTACH. Un proceso se conecta a la DLL, si es el primero que lo hace la DLL se carga en memoria
- DLL_THREAD_ATTACH. Un hilo se conecta a la DLL
- DLL_THREAD_DETACH. Un hilo se desconecta de la DLL
- DLL_PROCESS_DETACH. Un proceso se desconecta de la DLL, si este era el último proceso que la estaba usando, la DLL de descarga de la memoria (una DLL puede ser usada por varios procesos concurrentemente)
Pasos para escribir un plugin
EN RESUMEN
Obviamente, lo primero es diseñar el/los bloque/s DSP (los algoritmos) que vamos a implementar. Una vez que
sepamos lo que vamos a hacer, que comience el tecleo...
- Descarga el fichero ladspa.h
modificado e inclúyelo en el código de tu plugin (
#include "ladspa.h"
), además de todas las demás cabeceras que necesites,<stdlib.h>
,<math.h>
,<assert.h>
, etc - Define una estructura
Instance_data
para cada clase de bloque. - Escribe las funciones init() y fini() de la biblioteca, y en ellas crea la/s estructura/s
LADSPA_Descriptor
correspondientes a cada clase de bloque. - Escribe las funciones
instantiate
,activate
,connect
,deactivate
,cleanup
- Escribe la función
run
(o sus variantes), que es la que implementa el algoritmo DSP - Compila tu plugin (ver más adelante)
- Pruébalo en el host
Ejemplo básico: Amplificador
Escribamos el "Hola Mundo" de los plugins de audio. Un sistema que simplemente multiplica la señal de entrada
<math>x(n)</math> por una constante <math>b</math> obtenida de un puerto de control (en
este ejemplo sólo hay uno). La señal de salida <math>y(n)</math> es
<math>y(n) = bx(n) \ </math>
Fichero: amp.c
Si lo revisas verás que, de las 250 líneas de código que tiene, sólo las 3 últimas son las que hacen el
verdadero trabajo de DSP (función run
), todas las demás líneas son necesarias pero
irrelevantes para la tarea de DSP que queremos hacer. Digamos que es el número mínimo de líneas que ha de tener
el plugin, independientemente de su complejidad.
static void my_run(LADSPA_Handle hnd, unsigned long sample_count) { Instance_data* ins = (Instance_data*) hnd; LADSPA_Data* x = ins->port_i1; // input port LADSPA_Data* y = ins->port_o1; // output port LADSPA_Data b = *(ins->port_c1); // gain (input control port) unsigned long i; for (i = 0; i < sample_count; i++) { x[i] = y[i] * b; } }
Compilación:
¡Et voilà!, aquí tenemos nuestro plugin amp.dll
(libamp.so
en UNIX). Si usas UNIX o
Windows(MinGW) la opción -mno-cygwin
no es necesaria.
Probar el plugin
Puedes usar Audacity, que es un host LASDPA
multiplataforma. Copia amp.dll
al directorio Plugins del programa y ejecútalo (Inicio->Programas->Audacity).
En el menú "Efectos" debe aparecer nuestro nuevo plugin, selecciona un segmento de audio y pruébalo ;-)
aparecerá un diálogo en el podrás teclear un valor para el puerto de control PORT_C1 (ganancia).
Si quieres probar el plugin en tiempo real con micrófono y altavoz puedes usar RTLADSPA, que es un host muy simple que he escrito pensado para ese tipo de cosas, el siguiente ejemplo fija la ganancia en <math>b=2</math>
Lo de "tiempo real" aquí es discutible ya que la tarjeta de sonido y el sistema operativo siempre introducen latencia (retardo). Además, no es posible procesar la señal de entrada muestra a muestra sino bloque a bloque lo que introduce aun más latencia.
Sin embargo, hay muchos casos en audio digital donde no es imprescindible trabajar en tiempo real "estricto", lo que está claro es que esta es una forma simple (y barata) de hacer procesado de señal sin necesidad de comprar una tarjeta de desarrollo DSP (~500€).
Ejemplo básico: Control automático de la ganancia
Fichero: agc.c
Vamos a hacer una versión de este mismo ejemplo un poco más interesante: la ganancia, en vez de permanecer
constante, ahora la haremos variar en función de la potencia de la señal de entrada, amplificando más la señales
débiles y menos las más fuertes. En este caso no tenemos puerto de control porque nuestro bloque ya no recibe
parámetros, sólo hay puerto de señal de entrada y de salida.
<math>y(n) = b(n)x(n)</math>
Este es un ejemplo de algoritmo DSP adaptativo, la ganancia se actualiza así:
<math>b(n+1) = b(n) -
\mu\left(y(n)^2 - \sigma\right)\,</math>
- <math>\mu = 0.1</math> es el paso de adaptación, controla la velocidad de respuesta ante cambios bruscos en la potencia de la señal de entrada <math>x(n)</math>
- <math>\sigma = 0.002</math> es la potencia de referencia que queremos tener a la salida. El valor de las muestras de entrada típicamente suele estar en el rango de [-1, +1)
static void my_run(LADSPA_Handle hnd, unsigned long sample_count) { Instance_data* ins = (Instance_data*) hnd; LADSPA_Data* x = ins->port_i1; LADSPA_Data* y = ins->port_o1; unsigned long i; for (i = 0; i < sample_count; i++) { y[i] = x[i] * ins->b; ins->b = ins->b - 0.1 * (y[i]*y[i] - 0.002); } //printf("b = %f\n", ins->b); }
Compila y pruébalo
$ rtladspa --module agc.dll
Haz pruebas variando estos los parámetros <math>\mu</math> y <math>\sigma</math>, a ver qué pasa. También puedes descomentar la última línea para ver cómo evoluciona la ganancia en el tiempo y con la señal de entrada ;-)
Curiosidad: Un amplificador como este (pero implementado en HW, no en SW) lo tienen todos los receptores de radio para mantener siempre el mismo nivel de volumen de salida en el altavoz independientemente de lo fuerte que sea la señal recibida en la antena. A este circuito electrónico se le llama el Control Automático de Ganancia (AGC, Automatic Gain Control).
Escribir plugins LADSPA en otros lenguajes diferentes de C
Es perfectamente posible escribir un plugin en prácticamente cualquier lenguaje compilado: Pascal, D, Modula2,
etc. Basta con traducir el fichero C de cabecera ladspa.h
al nuevo lenguaje. Aquí tienes el ejemplo
del amplificador en otros lenguajes...
En Pascal (FreePascal)
Fichero: ladspa11_pascal.zip