Unity3D
Unity
audio

UnityのNativeAudioPluginを作る。

こんにちは。春から新入社員になりました。
Unityを触る機会が一気に増えたので、今まで気にはなりつつも触れてなかったNativeAudioPluginについて書いていこうと思います!

UnityのNativeAudioPluginについて

なぜかネット上だと異様に情報の少ないUnityのNativeAudioPluginですが、要点をまとめると以下のような感じです。

  • UnityのAudioMixerの機能を拡張する(VSTみたいな)
  • C++によって記述できて、OSごとにビルドして使う。(Windowsならdllに、Macならdylibに)
  • 上記に加えてGUIを作りたい場合はC#で記述してGUIを作る。(エディタ拡張に近いイメージ?)
  • Unityの公式サンプルがこれでもかというくらい充実している

公式サンプルにつきましては以下の記事で軽くまとめてくださっていますので、そちらをご参照ください。

ぶっちゃけゲーム用途だとこの公式サンプルがあればほとんどのことは事足りると思いますが、一応実験的に作ってみたので備忘録もかねて工程をご紹介します。

NativeAudioPluginの作り方

SDKのダウンロード

まずこれがなくては始まらないので、こちらから最新のSDKをダウンロードしてきましょう。
この中にサンプルも大量に含まれていますのでとりあえず解凍して遊びましょう。

プロジェクトファイルの立ち上げ

今回必要なのは先ほど解凍したメインフォルダの直下のNativeCodeというフォルダです。
スクリーンショット (11).png
さらにこの中にXcodeとVisualStudioのプロジェクトファイルが入っているので、環境に応じてどちらかを立ち上げてください。

コーディング!

実際に記述していくファイルはプラグインの内部処理を書く.cppファイル(新規作成)とプラグインのリストを書くファイル(PluginList.h)の二つだけです。
(GUIについては今回は触れません。)

内部処理の記述

まずは内部処理です。
今回はファイル名をPlugin_Demo_Distortion.cppとして以下のような単純なディストーションを作ってみました。

Plugin_Demo_Distortion.cpp
#include "AudioPluginUtil.h"
namespace Demo_Distortion
{
    enum Param
    {
        P_GAIN,
        P_THRESH,
        P_VOL,
        P_NUM
    };

    struct EffectData
    {
        struct Data
        {
            float p[P_NUM];
            float s;
            float c;
        };
        union
        {
            Data data;
            unsigned char pad[(sizeof(Data) + 15) & ~15]; // This entire structure must be a multiple of 16 bytes (and and instance 16 byte aligned) for PS3 SPU DMA requirements
        };
    };

    int InternalRegisterEffectDefinition(UnityAudioEffectDefinition& definition)
    {
        int numparams = P_NUM;
        definition.paramdefs = new UnityAudioParameterDefinition[numparams];
        RegisterParameter(definition, "GAIN", "", 1.0f, 10.0f, 1.0f, 1.0f, 1.0f, P_GAIN, "");
        RegisterParameter(definition, "THRESHOLD", "", 0.0f, 1.0f, 0.5f, 1.0f, 1.0f, P_THRESH, "");
        RegisterParameter(definition, "VOLUME", "", 0.0f, 2.0f, 1.0f, 1.0f, 1.0f, P_VOL, "");
        return numparams;
    }

    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;
    }

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

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

    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;
    }

    int UNITY_AUDIODSP_CALLBACK GetFloatBufferCallback(UnityAudioEffectState* state, const char* name, float* buffer, int numsamples)
    {
        return UNITY_AUDIODSP_OK;
    }

    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;
        for (unsigned int n = 0; n < length; n++)
        {
            for (int i = 0; i < outchannels; i++)
            {
                float out = inbuffer[n * outchannels + i] * data->p[P_GAIN];
                if (out > data->p[P_THRESH])
                {
                    out = data->p[P_THRESH];
                }
                else if (out < -data->p[P_THRESH])
                {
                    out = -data->p[P_THRESH];
                }
                outbuffer[n * outchannels + i] = out * data->p[P_VOL];
            }
        }
        return UNITY_AUDIODSP_OK;
    }
}

軽く解説をすると主に編集する必要があるのは上の方のパラメーター定義部分と一番下のProcessCallback関数です。

パラメーター定義の流れとしてはParam列挙型でまず使うパラメーターとその層数のインデックス番号を定義します。
次にInternalregisterEffectDifinition関数内でパラメーターを定義します。
以上です。

ProcessCallbackの中では入力値をGainパラメーターで増減しThreasholdパラメーターで上下をカット、最後にVolumeパラメーターでもう一度増減しています。

PluginList.hの記述

内部処理の記述ができたら仕上げにPluginList.h内に今回作ったプラグインを書き足します。

PluginList.h
DECLARE_EFFECT("Demo Equalizer", Equalizer)
DECLARE_EFFECT("Demo ImpactGenerator", ImpactGenerator)
DECLARE_EFFECT("Demo ImpulseGenerator", ImpulseGenerator)
DECLARE_EFFECT("Demo LevelMixer", LevelMixer)
DECLARE_EFFECT("Demo Lofinator", Lofinator)
DECLARE_EFFECT("Demo ModalFilter", ModalFilter)
DECLARE_EFFECT("Demo Multiband", Multiband)
DECLARE_EFFECT("Demo NoiseBox", NoiseBox)
DECLARE_EFFECT("Demo PitchDetector", PitchDetector)
DECLARE_EFFECT("Demo RingModulator", RingModulator)
DECLARE_EFFECT("Demo StereoWidener", StereoWidener)
DECLARE_EFFECT("Demo TeeBee3o3", TeeBee)
DECLARE_EFFECT("Demo TeeDee9o9", TeeDee)
DECLARE_EFFECT("Demo TubeResonator", TubeResonator)
DECLARE_EFFECT("Demo Vocoder", Vocoder)
DECLARE_EFFECT("Demo WahWah", WahWah)
DECLARE_EFFECT("Demo ConvolutionReverb", ConvolutionReverb)
DECLARE_EFFECT("Demo CorrelationMeter", CorrelationMeter)
DECLARE_EFFECT("Demo Granulator", Granulator)
DECLARE_EFFECT("Demo LoudnessMeter", LoudnessMeter)
DECLARE_EFFECT("Demo Oscilloscope", Oscilloscope)
DECLARE_EFFECT("Demo Routing", Routing)
DECLARE_EFFECT("Demo Spatializer", Spatializer)
DECLARE_EFFECT("Demo Spatializer Reverb", SpatializerReverb)

DECLARE_EFFECT("Demo Distortion", Demo_Distortion)

#if PLATFORM_OSX | PLATFORM_LINUX | PLATFORM_WIN
DECLARE_EFFECT("Demo Teleport", Teleport)
#endif

#if PLATFORM_OSX | PLATFORM_WIN
DECLARE_EFFECT("Demo Synthesizer", Synthesizer)
#endif

サンプルのプラグインのリストが記述されているのでその最後に今回作ったプラグインのUnityでの表示名とnamespaceを書き足します。

ビルド!

ここまで出来たらビルドしましょう。
ここであるあるですが使用PCに応じたbit数でビルドしないとUnityがエラーを出すので注意しましょう。
ビルド出来たらbuildフォルダの中にあるdllないしはdylibファイルをNativePluginを入れる要領でUnityに導入します。
あとはAudioMixerから動かすだけです!おつかれさまでした。

まとめ

今回はNativeAudioPluginの実装についてまとめてみました。
JUCEやMaxのgenなどでDSPの経験がある方はUnityなんでもいけるやんとなると思います。
ぶっちゃけ入力音を無視するプラグインを作ればシンセも作れます。
ただスクリプトからパラメーターをいじるのにひと手間必要だったりもするので今後の発展にも期待です。(あるのか...?)
Unityが音楽方面にも盛り上がることを切に願っております。