Version: 2017.2
Vista General del Uso y el API
Audio Spatializer SDK

Plugin SDK del Audio Nativo de Unity

Este documento describe la interfaz plugin del audio nativo integrado de Unity 5.0. Nosotros haremos esto al mirar algunos ejemplos de plugin específicos que crecen en complejidad a medida que avanzamos. De esta manera, nosotros comenzamos con conceptos muy básicos e introducimos casos de usuario más complejos cuando nos acerquemos al final del documento.

Descarga

Primero que todo usted va a necesitar descargar el SDK plugin más nuevo de Audio aquí.

Información General

El sistema de plugin de audio nativo consisted de dos partes:

  1. El plugin DSP nativo (Digital Signal Processing-Procesamiento de Señal Digital) que debe ser implementado como .dll (Windows) o .dylib (OSX) en C o C++. A diferencia de scripts, y debido a la alta demanda de rendimiento, esto debe ser compilado para cualquier plataforma que usted quiera soportar, posiblemente con optimizaciones de la plataforma especificas.

  2. El GUI que es desarrollado en C#. Tenga en cuenta que el GUI es opcional, por lo que usted siempre empieza el desarrollo del plugin al crear el plugin DSP nativo básico, y deja que Unity muestre un UI basado en un deslizador por defecto para las descripciones de parámetro las cuales el plugin nativo expone. Nosotros recomendamos este acercamiento para bootstrap cualquier proyecto.

Tenga en cuenta que usted inicialmente puede prototipar el GUI C# como un archivo .cs que usted acaba de soltar en la carpeta de Assets/Editor (tal como cualquier otro script del editor). Más adelante, usted puede mover esto a un proyecto MonoDevelop propio a medida que su código comience a crecer y necesite una modularización mejor y un soporte IDE mejor. Esto le permite a usted compilarlo a .dll, haciéndole al usuario más fácil soltarlo al proyecto y también con el fin de proteger su código.

También tenga en cuenta que ambos los DSP nativos y DLLS del GUI pueden contener múltiples plugins y que la union sucede solo a través de los nombres de los efectos en los plugins sin importar como se llame el archivo DLL.

Qué son todos estos archivos?

El lado nativo del SDK plugin solo consiste de un archivo (AudioPluginInterface.h), pero para hacerlo fácil de tener múltiples efectos de plugin dentro el mismo DLL nosotros hemos agregado un código de soporte para manejar la definición de efectos y registro de parámetros en una manera unificada y simple (AudioPluginUtil.h y AudioPluginUtil.cpp). Tenga en cuenta que el proyecto NativePluginDemo contiene un número de plugins ejemplos para ayudarle a empezar y mostrar una variedad de diferentes tipo de plugin que son útiles en un contexto de juego. Nosotros colocamos este código en el dominio público, por lo que siéntase libre de usar este código como un punto de inicio para sus propias creaciones.

El desarrollo de un plugin comienza con definir qué parámetros su plugin debería tener. Usted no necesita tener un plan maestro detallado de todos los parámetros que el plugin tendrán antes de comenzar, pero ayuda mucho tener una idea de cómo usted quiere que sea la experiencia de usuario y qué componentes va a necesitar.

Los plugins de ejemplos que nosotros proporcionamos tienen una cantidad de funciones de utilidad que lo hacen fácil Echemos un vistazo al plugin ejemplo “Ring Modulator”. Este plugin simple múltiple la señal que entra por una onda seno, la cual da un bonito sonido de radio / efecto de una recepción rota, especialmente si múltiples efectos del ring modulation (la modulación en anillo) con diferentes frecuencias son encadenados.

El esquema básico para tratar con parámetros en los plugins de ejemplo es definirlos como valores-enum que nosotros usamos como indices en un arreglo de floats tanto para conveniencia y brevedad.

enum Param
{
    P_FREQ,
    P_MIX,
    P_NUM
};

int InternalRegisterEffectDefinition(UnityAudioEffectDefinition& definition)
{
    int numparams = P_NUM;
    definition.paramdefs = new UnityAudioParameterDefinition [numparams];
    RegisterParameter(definition, "Frequency", "Hz",
        0.0f, kMaxSampleRate, 1000.0f,
        1.0f, 3.0f,
        P_FREQ);
    RegisterParameter(definition, "Mix amount", "%",
        0.0f, 1.0f, 0.5f,
        100.0f, 1.0f,
        P_MIX);
    return numparams;
}

Los números en los llamados RegisterParameter son mínimo, máximo, y valores por defecto seguido de un factor de escala utilizado solo para mostrarlo, i.e. en el caso de un valor porcentual, el valor actual va desde 0 a 1 y es escalado por 100 cuando es mostrado. No hay un código GUI personalizado para esto, pero como se menciono antes, Unity va a generar un GUI por defecto de estas definiciones de parámetro básicas. Tenga en cuenta que ninguna revisión es realizada para parámetros no definido, por lo que el sistema AudioPluginUtil espera que todos los valores enum declarados (excepto P_NUM) coincidan con una definición de parámetro correspondiente.

Detrás de escenas la función RegisterParameter llena una entrada en el arreglo UnityAudioParameterDefinition de la estructura UnityAudioEffectDefinition que es asociada con ese plugin (ver “AudioEffectPluginInterface.h”). El resto que necesitan configurarse en UnityAudioEffectDefinition son los callbacks a las funciones que manejan la instanciación del plugin (CreateCallback), configurando/obteniendo parámetros (SetFloatParameterCallback/UnityAudioEffect_GetFloatParameterCallback), haciendo el procesamiento actual (UnityAudioEffect_ProcessCallback) y eventualmente destruyendo la instancia del plugin cuando haya finalizado (UnityAudioEffect_ReleaseCallback).

Para hacerlo fácil de tener múltiples plugins en el mismo DLL, cada plugin reside en su propio namespace, y una convención específica de nombramiento para las funciones callbacks es utilizada tal que las macros DEFINE_EFFECT y DECLARE_EFFECT puedan llenar la estructura UnityAudioEffectDefinition. Debajo del capote, todas las definiciones de efectos son almacenados en un arreglo el cual un apuntador es devuelto por el único punto de entrada de la librería UnityGetAudioEffectDefinitions.

Esto es útil conocerlo en caso que usted quiere desarrollar plugins puentes que mapeen de otros formatos de plugin como VST o AudioUnits para o desde la interfaz de plugin de audio de Unity, en tal caso usted necesita desarrollar una manera más dinámica de configurar las descripciones de los parámetros en tiempo de carga.

Instanciando el plugin

La siguiente cosa es los datos para la instancia del plugin. En los plugins de ejemplo, nosotros colocamos todo esto a la estructura EffectData. La asignación de esto debe suceder en el CreateCallback correspondiente que es llamado para cada instancia del plugin en el mezclador. En este ejemplo simple, hay solo una onda seno que es multiplicada a todos los canales, otros plugins más avanzados necesitan asignar los datos adiciones por canal de input.

struct EffectData
{
    struct Data
    {
        float p[P_NUM]; // Parameters
        float s;        // Sine output of oscillator
        float c;        // Cosine output of oscillator
    };
    union
    {
        Data data;
        unsigned char pad[(sizeof(Data) + 15) & ~15];
    };
};
UNITY_AUDIODSP_RESULT UNITY_AUDIODSP_CALLBACK CreateCallback(
    UnityAudioEffectState* state)
{
    EffectData* effectdata = new EffectData;
    memset(effectdata, 0, sizeof(EffectData));
    effectdata->data.c = 1.0f;
    state->effectdata = effectdata;
    InitParametersFromDefinitions(
        InternalRegisterEffectDefinition, effectdata->data.p);
    return UNITY_AUDIODSP_OK;
}

El UnityAudioEffectState contiene varios datos del host (anfitrión) tal como la velocidad de muestreo, el número total de muestras procesadas (por tiempo), o si el plugin es bypassed (omitido), y es pasado a todas las funciones de callback.

Y obviamente para liberar la instancia del plugin la cual tiene una función correspondiente también.

UNITY_AUDIODSP_RESULT UNITY_AUDIODSP_CALLBACK ReleaseCallback(
    UnityAudioEffectState* state)
{
    EffectData::Data* data = &state->GetEffectData<EffectData>()->data;
    delete data;
    return UNITY_AUDIODSP_OK;
}

El procesamiento principal del audio sucede en ProcessCallback:

UNITY_AUDIODSP_RESULT UNITY_AUDIODSP_CALLBACK ProcessCallback(
    UnityAudioEffectState* state,
    float* inbuffer, float* outbuffer,
    unsigned int length,
    int inchannels, int outchannels)
{
    EffectData::Data* data = &state->GetEffectData<EffectData>()->data;
 
    float w = 2.0f * sinf(kPI * data->p[P_FREQ] / state->samplerate);
    for(unsigned int n = 0; n < length; n++)
    {
        for(int i = 0; i < outchannels; i++)
        {
            outbuffer[n * outchannels + i] =
                inbuffer[n * outchannels + i] *
                (1.0f - data->p[P_MIX] + data->p[P_MIX] * data->s);
        }
        data->s += data->c * w; // cheap way to calculate a sine-wave
        data->c -= data->s * w;
    }
 
    return UNITY_AUDIODSP_OK;
}

Hemos quitado las partes específicas de PS3 de esta función en la lista. La función GetEffectData en la parte superior es solo una función de ayuda que hace cast en el campo de effectdata de la variable de estado a la EffectData::Data en la estructura que hemos declarado arriba.

Otros plugins simples incluidos son el plugin NoiseBox, que agrega y multiplica la señal de input por un sonido blanco (white noise) en frecuencias que varían, o el plugin Lofinator, que hace un simple downsampling y cuantización de la señal. Todos estos pueden ser utilizados en combinación y con parámetros animados llevados por el nuevo para simular cualquier cosa desde teléfonos móviles hasta una recepción mala de radio en walkie talkies, alta voces rotos etc.

El StereoWidener, que descompone una señal de input estero a mono y componentes secundarios con una variable de retraso y luego re-combina estos para aumentar el efecto estero percibido.

Una cantidad de plugins simples sin GUIs personalizados para comenzar.
Una cantidad de plugins simples sin GUIs personalizados para comenzar.

Qué plugin cargar en qué plataforma?

Los plugins de audio nativos utilizan el mismo esquema que otros plugins nativo o manejados en la manera en que deben estar asociados con su respectiva plataforma, vía el inspector plugin importer. Usted puede leer más acerca de sub-carpetas en las cuales colocar los plugins aquí. La asociación de plataforma es necesaria para que el sistema sepa qué plugins incluir en cada objetivo de construcción en las construcciones standalone, y con la introducción de soporte 64-bit esto incluso tiene que se especificado dentro de una plataforma. Los plugins OSX son especiales es esto desde que el formato Universal Binary le permite a ellos contener ambas variantes 32 y 64 bit en el mismo bundle (conjunto).

Los plugins Nativos de Unity que son llamados desde un código manejado son cargados vía el atributo [DllImport] referenciando la función en ser importada del DLL nativo. Sin embargo, en el caso de plugins nativos de audio las cosas son diferentes. El problema especial que surge aquí es que los plugins de audio necesitan ser cargados antes de que Unity comience a crear cualquier asset que mezcle que pueda necesitar efectos de los plugins. En el editor esto no es un problema, ya que nosotros simplemente podemos re-cargar los mezcladores que dependan en plugins, pero en construcciones standalone, los plugins deben ser cargados antes de que creemos los assets de mezcla. Para resolver esto, la convención actual es dar un prefijo al DLL del plugin “audioplugin” (sensible a mayúsculas) para que el sistema pueda detectar esto y lo agregue a una lista de plugins que serán cargados automáticamente en el inicio. Recuerde que solo son las definiciones dentro del plugin que definen los nombres de los efectos que son mostrados dentro del mezclador de Unity, para que el DLL puede ser llamado cualquier cosa, pero necesita iniciar con el string “audioplugin” para que pueda ser detectado como tal.

Para plataformas tales como IOS el código de plugin necesitar ser vinculado de manera estática al binario de Unity producido por el proyecto Xcode generado y ahí - tal como dispositivos de renderizado de plugins - el registro del plugin necesita ser agregado explícitamente al código de inicio de la app.

En OSX un bundle (conjunto) puede contener ambas versiones 32- y 64 bit del plugin. Usted también las puede separar para ahorrar tamaño.

Plugins con GUIs personalizados

Ahora echemos un vistazo a algo más avanzado: Effectos para ecualización y compresión multibanda. Tales plugins tiene un mayor número de parámetros que un plugin simple presentado en la sección previo y también hay algo de acoplamiento físico entre los parámetros que requieren una mejor manera de visualizar los parámetros que simplemente un monto de deslizadores simples. Considere un ecualizador por ejemplo: Cada banda tiene tres diferentes filtros que de manera colectiva contribuyen a la curva de ecualización final y cada uno de estos filtros tienen 3 parámetros frequency (frecuencia), Q-factor y gain que son fisicamente vinculados y definen la forma de cada filtro. Por lo que ayuda al usuario mucho, si un plugin de ecualización tiene una pantalla grande y bonita mostrando la curva resultante, las contribuciones de filtro individuales pueden ser operadas de tal manera que múltiplos parámetros pueden ser configurados simultáneamente simplemente arrastrando las operaciones en el control en vez de cambiar los deslizadores uno a la vez.

GUI Personalizado del plugin Ecualizador. Arrastre las tres bandas para cambiar los gains y frecuencias del filter curve (curva de filtro) . Mantenga shift presionado mientras arrastre para cambiar la forma de cada banda.

Entonces nuevamente, la definición, inicialización, de-inicialización y el manejo de parámetros sigue exactamente el mismo método basado en enum que los simples plugins utilizan, e incluso el código ProcessCallback es bastante corto. Bueno, tiempo de parar de mirar el código nativo y abrir el proyecto AudioPluginDemoGUI en MonoDevelop. Aquí usted encontrará las clases C# asociadas para el código GUI. La forma en que funciona es simple: Una vez Unity haya cargado los plugins DLLS nativos y registre los plugins de audio contenidos, va a comenzar a buscar por GUIS correspondientes que coincidan con los nombres registrados de los plugins. Esto sucede a través de la propiedad Name de la clase EqualizerCustomGUI que, como todos los plugins de GUIS personalizados, debe ser heredado de IAudioEffectPluginGUI. Solo hay una función importante dentro de la clase que es la función bool OnGUI(IAudioEffectPlugin plugin), Vía el argumento plugin IAudioEffectPlugin, esta función pone un identificador al plugin nativo que puede ser utilizado para leer y escribir parámetros que el plugin nativo ha definido. Por lo que para leer un parámetro llama:

plugin.GetFloatParameter("MasterGain", out masterGain);

que devuelve true si el parámetro fue encontrado, y para configurarlo, llama:

plugin.SetFloatParameter("MasterGain", masterGain);

que también devuelve true si el parámetro existe. Y eso es básicamente la unión más importante entre el GUI y el código nativo. Usted también puede utilizar la función

plugin.GetFloatParameterInfo("NAME", out minVal, out maxVal, out defVal);

para consultar el parámetro “NAME” para sus valores mínimos, máximos y por defecto para evitar duplicar definiciones de estas en el código nativo y UI.

nosotros no vamos a discutir los detalles acerca del procesamiento DSP que se está realizando en los plugins Equalizer (Ecualizador) y Multiband (multi banda) aquí, para aquellos interesado, los filtros son tomados del excelente Audio EQ Cookbook de Robert Bristow Johnson y para colocar las curvas Unity proporciona algunas funciones internas del API para dibujar curvas anti-aliased para la repuesta de frecuencia.

Una cosa más para mencionar es que ambos los plugins Equalizer y Multiband también proporcionan código para superponer los espectros de input y output para visualizar el efecto de los plugins, lo que trae un punto interesante: El código GUI corre en una velocidad de actualización más baja (la velocidad de frame) que el procesamiento de audio y no tiene acceso a los flujos de audio, por lo que cómo leemos estos datos? Para esto, hay una función en especial para hacer esto en el código nativo:

UNITY_AUDIODSP_RESULT UNITY_AUDIODSP_CALLBACK GetFloatParameterCallback(
    UnityAudioEffectState* state,
    int index,
    float* value,
    char *valuestr)
{
    EffectData::Data* data = &state->GetEffectData<EffectData>()->data;
    if(index >= P_NUM)
        return UNITY_AUDIODSP_ERR_UNSUPPORTED;
    if(value != NULL)
        *value = data->p[index];
    if(valuestr != NULL)
        valuestr[0] = 0;
  return UNITY_AUDIODSP_OK;
}

Simplemente activa leer un arreglo de datos de punto flotantes del plugin nativo. Lo que diga los datos, el sistema de plugin no le importa, hasta que el pedido no disminuya masivamente el UI o el código nativo. Para el código Equalizer y Multiband hay una clase de utilidad llamada FFTAnalyzer que hace que sea fácil alimentar los datos de input y output del plugin y obtenga un espectro devuelta. Estos datos de espectro son luego re-sampleados (muestreados) por GetFloatBufferCallback y entregados al código UI C#. La razón por la cual los datos necesitan ser re-muestreados es que el FFTAnalyzer corre el análisis en una resolución de frecuencia fija mientras que GetFloatBufferCallback simplemente devuelve el número de muestras pedidas, las cuales son determinadas por el ancho de la vista que está mostrando los datos. Para un plugin simple que tiene la cantidad mínima de código DSP usted podría echar un vistazo al plugin CorrelationMeter, que simplemente traza la amplitud del canal de la izquierda contra la amplitud del canal de la derecha con el fin de mostrar “qué tan stereo” la señal está

Izquierda: GUI personalizado del plugin CorrelationMeter.

Derecho: GUI Equalizer con un análisis de espectro superpuesto (la curva verde es fuente, la roja es procesada).

En este punto nosotros también quisiéramos decir que ambos los efectos Equalizer y Multiband son intencionalmente mantenidos simples y no-optimizados, pero nosotros pensamos que estos sirven como buenos ejemplos de UIs más complejos que son soportados por el sistema de plugin. Hay obviamente una cantidad de trabajo todavía en hacer las optimizaciones especificas relevantes para las plataformas, toneladas de ajustes de parámetros para hacer que se sienta bien y responde en la manera más musical posible etc… Nosotros podríamos también implementar algunos de estos efectos como plugins integrados en Unity en algún punto simplemente por conveniencia de aumentar el repertorio estándar de plugins de Unity, pero nosotros sinceramente esperamos que el lector también tome el reto de hacer algunos plugins muy buenos – y quién sabe, podría en un punto terminar como un plugin integrado. ;-)

Plugin ejemplo de reverb de convolución (Convolution reverb). La respuesta de impulso está descomponiendo ruido aleatorio, definido por los parámetros. Esto es solo para propósitos de demostración, como un plugin de producción debería permitirle al usuario cargar impulsos grabados de manera arbitraria, el algoritmo de convolución subyacente sin embargo, se mantiene igual.

Ejemplo de una herramienta de monitoreo del ruido midiendo niveles en 3 diferentes escalas de tiempo. También solo para propósitos de demostración, pero es un buen inicio para comenzar a construir una herramienta de monitoreo que se conforme a una estandarización de ruido moderno. El código de renderización de la curva está construido en Unity.

Sincronizando al reloj DSP

Tiempo para algunos ejercicios divertidos. Por qué no utilizar el sistema de plugin para generar sonido en vez de procesarlo? Intentemos hacer una linea de bajo simple y algunos sintetizadores de batería que podría ser familiar a personas que escucha acid trance – algunos clones simples de algunos de los synths principales que definen este género. Eche un vistazo a Plugin_TeeBee.cpp y Plugin_TeeDee.cpp. Estos synths simples simplemente generan patrones con notas aleatorias y tienen algunos parámetros para ajustar los filtros, sobres y así en el motor del sintetizador. Nuevamente, nosotros no vamos a discutir esos detalles aquí, pero simplemente apuntar que el parámetro state->dsptick es leído en el ProcessCallback con el fin de determinar la posición en la “canción”. Este contador es una posición de muestra global, por lo que nosotros simplemente lo dividimos por la longitud de cada nota especificada en las muestras y disparamos un evento de nota al motor de sintetizador cuando esta división tiene un residuo de cero. De esta manera, todos los efectos del plugin se mantienen en sincronización al mismo reloj basado en muestras, y si usted por ejemplo reproduce una pieza per-grabada de música con un tempo conocido a través de tal efecto, usted puede utilizar la información del tiempo para aplicar unos efectos de filtro de sincronización con tempo o retrasos en la música.

Una linea de bajo simple y baterias sintetizadas para demostrar efectos sincronizados con tempo.
Una linea de bajo simple y baterias sintetizadas para demostrar efectos sincronizados con tempo.

Spatialization (Espacialización)

El audio plugin SDK nativo es la base del Spatialization SDK que permite el desarrollo de efectos de espacialización personalizados que son instanciados por audio source. Más información acerca de esto se puede encontrar aquí.

Outlook

Este es simplemente el inicio de un esfuerzo para abrir las partes del sistema de sonido a código nativo de alto rendimiento. Nosotros tenemos planes para integrar esto en otras partes de Unity también, al igual que hacer los efectos utilizables afuera del mezclador al igual que extender el SDK para soportar otros tipos de parámetros diferentes a floats con soporte a unos mejores GUIs por defectos al igual que el almacenamiento de datos binarios.

Ahora diviértase mucho creando sus propios plugins. Esperamos verlos en el asset store. ;-)

“Disclaimer”

While there are many similarities in the design, Unity’s native audio SDK is not built on top of other plugin SDKs like Steinberg VST or Apple AudioUnits. It should be mentioned that it would be easy for the interested reader to implement basic wrappers for these using this SDK that allow using such plugins to be used in Unity. It is not something the dev-team of Unity is planning to do. Proper hosting of any plugin quickly gets very complex, and dealing with all the intricacies of expected invocation orders and handling custom GUI windows that are based on native code quickly grows by leaps and bounds which makes it less useful as example code.

While we do understand that it could potentially be quite useful to load your VST or AU plugin or even effects for just mocking up / testing sound design, bear in mind that using VST/AU also limits you to a few specific platforms. The potential of writing audio plugins based on the Unity SDK is that it extends to all platforms that support software-mixing and dynamically loaded native code. That said, there are valid use cases for mocking up early sound design with your favourite tools before deciding to devote time to develop custom plugins (or simply to be able to use metering plugins in the editor that don’t alter the sound in any way), so if anyone wants to make a nice solution for that, please do.

Vista General del Uso y el API
Audio Spatializer SDK