Javier Valcarce's Homepage

Plugin LADSPA básico

Escribir un plugin de audio te permite crear tus propios efectos de audio y usarlos en audacity u otros programas de audio. Este artículo muestra cómo escribir un plugin básico, una especie de "hola mundo".

Requisitos previos

Discrete sin.png
  • 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:

  1. 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
  2. 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

Ladspa 1.png

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:

Puertos en un plugin LADSPA
  • 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 ;-)

Campos de la estructura LADSPA_Descriptor
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...

  1. 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
  2. Define una estructura Instance_data para cada clase de bloque.
  3. 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.
  4. Escribe las funciones instantiate, activate, connect, deactivate, cleanup
  5. Escribe la función run (o sus variantes), que es la que implementa el algoritmo DSP
  6. Compila tu plugin (ver más adelante)
  7. 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>

DSP y bx.png

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:

$ gcc -mno-cygwin -DBUILD_DLL -shared -o amp.dll -L./ amp.c

¡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>

$ rtladspa --module amp.dll --parameters 2

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)
DSP y bx adaptive.png


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

$ gcc -mno-cygwin -DBUILD_DLL -shared -o agc.dll -L./ agc.c
$ 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

This page forms part of website https://javiervalcarce.eu