LADSPA Delay

LADSPAのプラグインを実験的に作ってみる。今回はシンプルなディレイで、msecでディレイタイムを設定し、dBでDry,Wetのレベル調整を行い、フィードバックは-1から1というオーソドックスなもの。LADSPAにあったサンプルのディレイはフィードバックもなくsec単位、パラメータはリニア単位だったので、その辺を改造してみた。DryのON/OFFも追加。実験用に使うので、パラメータは少なくして基本的にモノラル用とした。LADSPAは初期設定値が細かくコントロールできないので、パラメーターが多いと面倒になる。3つぐらいの入力であればストレスはないので実用になる。

VSTもそうだが、この手のプラグインは、パラメーター含めてfloatで管理していたりして、個人的には窮屈に感じる。やっぱりフルオリジナルの方が面白いわ。ただ0から作る必要がないので、ちょろっと試すには手軽かもしれない。LADSPAはシンプルな仕様なので、ladspa.hを眺めていれば、何となく出来てしまうよさはある。

ブロック図は極めてシンプル。


Delayの自作は何度もやっていた

Audacity標準のEchoやNyquistで作られたDelayは使い勝手が悪かったので、何度かいろいろなプログラム言語で作ってきた。はじめにAudacityとの親和性が高いNyquistでささっと作ってみたが、これはfeedbackの概念がちょっと違ったり、Dryをうまくコントロールできず、個人的にはしっくりこなかった。その後Javaで思い通りコントロールするDelayを作ったのだが、Audacity内でちょこっと使うという用途ではない。LADSPAはC言語&floatを使ってローレベルの制御が出来るので、そこそこの自由度があり、この程度のエフェクトであれば好都合に思えた。

ソースはビット演算を使ったディレイ。バッファは構造体に設置しないとうまく動作しなかった。LADSPAの仕様がよく分かっていないのだが、いろいろ制約があるようだ。

LADSPA Delay

/* Namagi delay.c 
2013.01.29
windows compile
gcc -shared -o namagi_delay.dll namagi_delay.c -ID

compile Ubuntu
gcc -fPIC -DPIC -shared -nostartfiles 
-o namagi_delay.so namagi_delay.c
*/
/***********************************************************/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "ladspa.h"
/***********************************************************/
#define INPUT       0
#define OUTPUT      1
#define DELAY_TIME  2
#define FEEDBACK    3
#define WET         4
#define DRYCHECK    5
#define DRY         6
/***********************************************************/
/* Win用設定 */
#ifdef WIN32
    int bIsFirstTime = 1;
    void _init();
    #define _WINDOWS_DLL_EXPORT_ __declspec(dllexport)
#else
    #define _WINDOWS_DLL_EXPORT_ 
#endif
/***********************************************************/
typedef struct {
    unsigned long m_lSampleRate;
    unsigned long m_lBufferSize;
    unsigned long m_lWritePointer;
    float *m_pfBuffer;
    float *m_pfDelay;
    float *m_pfFeedback;
    float *m_pfWet;
    float *m_pfDryCheckBox;
    float *m_pfDry;
    float *m_pfInput;
    float *m_pfOutput;
} Delay;
/***********************************************************/
LADSPA_Handle 
instantiateDelay(const LADSPA_Descriptor *Descriptor,
    unsigned long SampleRate){
    
    Delay *psDelay;
    psDelay = (Delay *)malloc(sizeof(Delay));
    if (psDelay == NULL) return NULL;
    
    /*サンプリング周波数*/
    psDelay -> m_lSampleRate = SampleRate;

    /*シフト演算 固定maxの1秒確保 
    65536tap 2のべき乗 ビット演算を利用するため */
    psDelay -> m_lBufferSize = 1;
    while (psDelay -> m_lBufferSize < SampleRate){
        psDelay -> m_lBufferSize <<= 1;
    }
    /*メモリ確保 65536tap */
    psDelay -> m_pfBuffer =
    (float *)calloc(psDelay -> m_lBufferSize, sizeof(float));
    if (psDelay -> m_pfBuffer == NULL) {
        free(psDelay);
        return NULL;
    }
    psDelay -> m_lWritePointer = 0;
    return psDelay;
}
/***********************************************************/
void activateDelay(LADSPA_Handle Instance){
    Delay *psDelay;
    psDelay = (Delay *)Instance;
    /* バッファを0で埋める 初期化 */
    memset(psDelay -> m_pfBuffer, 0,
    sizeof(float) *psDelay -> m_lBufferSize);
}
/***********************************************************/
void connectPortToDelay(LADSPA_Handle Instance,
    unsigned long Port,float *DataLocation){
    Delay * psDelay;
    psDelay = (Delay *)Instance;
  
    switch (Port) {
        case DELAY_TIME:
            psDelay -> m_pfDelay = DataLocation;
            break;
        case FEEDBACK:
            psDelay -> m_pfFeedback = DataLocation;
            break;
        case WET:
            psDelay -> m_pfWet = DataLocation;
            break;
        case DRYCHECK:
            psDelay -> m_pfDryCheckBox = DataLocation;
            break;
        case DRY:
            psDelay -> m_pfDry = DataLocation;
            break;
        case INPUT:
            psDelay -> m_pfInput = DataLocation;
            break;
        case OUTPUT:
            psDelay -> m_pfOutput = DataLocation;
            break;
    }
}
/***********************************************************/
void runDelay(LADSPA_Handle Instance,unsigned long SampleCount){

    Delay *psDelay;
    psDelay = (Delay*) Instance;
    
    float *pfInput = psDelay -> m_pfInput;
    float *pfOutput = psDelay -> m_pfOutput;
    float *pfBuffer = psDelay -> m_pfBuffer;
    
    float fInputSample;
    float fWet = *(psDelay -> m_pfWet);
    float fFeedback = *(psDelay -> m_pfFeedback);
    float fDryCheckBox = *(psDelay -> m_pfDryCheckBox);
    float fDry = *(psDelay -> m_pfDry);

    /*バッファサイズ2のべき乗に対して1を引く
    65535
    ビットで見ると0b1111111111111111 ビット演算で利用 */
    unsigned long lBufferSizeMinusOne = psDelay -> m_lBufferSize - 1;
    /*ディレイタイムにサンプルレートを掛けてタップ数を出している*/
    unsigned long lDelay
     = *(psDelay -> m_pfDelay) * psDelay -> m_lSampleRate / 1000;
    /* 書き込みポジション 0からスタート */
    unsigned long lBufferWriteOffset = psDelay -> m_lWritePointer;
    /* 読み込みポジション
    0 + 65536 - 44100(delaytime 1秒なら) = 21436 */
    unsigned long lBufferReadOffset
     = lBufferWriteOffset + psDelay -> m_lBufferSize - lDelay;
    unsigned long i;

    /*ディレイ処理*/
    for (i=0; i < SampleCount; i++){
        fInputSample = *(pfInput++);
        if(fDryCheckBox == 1){
            *(pfOutput++) 
            = pow(10,fDry/20.0) * fInputSample
            + pow(10,fWet/20.0) 
            /* 0 + 21436 - 65535 = 21436
            &以降 ビット演算子 両辺が1のときのみ1になる
            i=0 → 21436 スタート位置
            i=44099 → 65535 バッファの最後
            i=44100 → 0 バッファの最初に戻る
            */
            * pfBuffer[(i + lBufferReadOffset) & lBufferSizeMinusOne];
        }else{
            *(pfOutput++) 
            = pow(10,fWet/20.0) 
            * pfBuffer[(i + lBufferReadOffset) & lBufferSizeMinusOne];
        }
        /* 書き込み位置は0から */
        pfBuffer[(i + lBufferWriteOffset) & lBufferSizeMinusOne] 
        = fInputSample 
        + fFeedback * pfBuffer[(i +lBufferReadOffset) & lBufferSizeMinusOne];
    }
    /* 最後に構造体にバッファ位置を戻している LADSPAの仕様? */
    psDelay -> m_lWritePointer 
    = ((psDelay -> m_lWritePointer + SampleCount) &lBufferSizeMinusOne);
}
/***********************************************************/
void cleanupDelay(LADSPA_Handle Instance){
    Delay * psDelay;
    psDelay = (Delay *)Instance;
    //free(psDelay -> m_pfBuffer);
    free(psDelay);
}
/***********************************************************/
LADSPA_Descriptor *g_psDescriptor = NULL;
/***********************************************************/
void _init() {
    char ** pcPortNames;
    LADSPA_PortDescriptor * piPortDescriptors;
    LADSPA_PortRangeHint * psPortRangeHints;
    int ports = 7;
    
    g_psDescriptor = (LADSPA_Descriptor *)malloc(sizeof(LADSPA_Descriptor));

    if (g_psDescriptor) {
    g_psDescriptor->UniqueID = 0;
    g_psDescriptor->Label = strdup("delay");
    g_psDescriptor->Properties = LADSPA_PROPERTY_HARD_RT_CAPABLE;
    g_psDescriptor->Name = strdup("Namagi: Delay ver.130129");
    g_psDescriptor->Maker = strdup("Namagi Products");
    g_psDescriptor->Copyright = strdup("None");
    g_psDescriptor->PortCount = ports;
    
    /* メモリ*/
    piPortDescriptors =
        (LADSPA_PortDescriptor *)calloc(ports,sizeof(LADSPA_PortDescriptor));
    g_psDescriptor -> PortDescriptors
    = (const LADSPA_PortDescriptor *)piPortDescriptors;
       
    /* ポート */
    piPortDescriptors[DELAY_TIME] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
    piPortDescriptors[DRYCHECK] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
    piPortDescriptors[DRY] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
    piPortDescriptors[WET] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
    piPortDescriptors[FEEDBACK] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
    piPortDescriptors[INPUT] = LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO;
    piPortDescriptors[OUTPUT] = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO;

    pcPortNames = (char **)calloc(ports, sizeof(char *));
    g_psDescriptor->PortNames = (const char **)pcPortNames;

    pcPortNames[DELAY_TIME] = strdup("delay msec.");
    pcPortNames[FEEDBACK] = strdup("feedback");
    pcPortNames[WET] = strdup("wet dB");
    pcPortNames[DRYCHECK] = strdup("dry on/off");
    pcPortNames[DRY] = strdup("dry dB");
    pcPortNames[INPUT] = strdup("input");
    pcPortNames[OUTPUT] = strdup("output");
    
    /*  */
    psPortRangeHints
     = ((LADSPA_PortRangeHint *)calloc(ports, sizeof(LADSPA_PortRangeHint)));
    g_psDescriptor -> PortRangeHints
     = (const LADSPA_PortRangeHint *)psPortRangeHints;
       
    /*  */
    psPortRangeHints[DELAY_TIME].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW | 
      LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_LOW);
    psPortRangeHints[DELAY_TIME].LowerBound = 0;
    psPortRangeHints[DELAY_TIME].UpperBound = 1000.0; 
    psPortRangeHints[FEEDBACK].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW 
      | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_HIGH);
    psPortRangeHints[WET].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW 
      | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_0);

    /*  */
    psPortRangeHints[DRYCHECK].HintDescriptor = 
      (LADSPA_HINT_TOGGLED | LADSPA_HINT_DEFAULT_0);

    psPortRangeHints[DRY].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW 
      | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_0);

    psPortRangeHints[DRY].LowerBound = -120;
    psPortRangeHints[DRY].UpperBound = 0;
    psPortRangeHints[WET].LowerBound = -120;
    psPortRangeHints[WET].UpperBound = 0;
    psPortRangeHints[FEEDBACK].LowerBound = -1;
    psPortRangeHints[FEEDBACK].UpperBound = 1;
    psPortRangeHints[INPUT].HintDescriptor = 0;
    psPortRangeHints[OUTPUT].HintDescriptor = 0;

    g_psDescriptor -> instantiate = instantiateDelay;
    g_psDescriptor -> connect_port =  connectPortToDelay;
    g_psDescriptor -> activate = activateDelay;
    g_psDescriptor -> run = runDelay;
    g_psDescriptor -> run_adding = NULL;
    g_psDescriptor -> set_run_adding_gain = NULL;
    g_psDescriptor -> deactivate = NULL;
    g_psDescriptor -> cleanup = cleanupDelay;
    }
}
/***********************************************************/
void _fini() {
    long lIndex;
    if (g_psDescriptor) {
        free((char *)g_psDescriptor -> Label);
        free((char *)g_psDescriptor -> Name);
        free((char *)g_psDescriptor -> Maker);
        free((char *)g_psDescriptor -> Copyright);
        free((LADSPA_PortDescriptor *)g_psDescriptor -> PortDescriptors);
        for (lIndex = 0; lIndex < g_psDescriptor -> PortCount; lIndex++)
            free((char *)(g_psDescriptor -> PortNames[lIndex]));
        free((char **)g_psDescriptor -> PortNames);
        free((LADSPA_PortRangeHint *)g_psDescriptor -> PortRangeHints);
        free(g_psDescriptor);
    }
}
/***********************************************************/
_WINDOWS_DLL_EXPORT_
const LADSPA_Descriptor *ladspa_descriptor(unsigned long Index){
#ifdef WIN32
    if (bIsFirstTime) {
        _init();
        bIsFirstTime = 0;
    }
#endif
    if (Index == 0)
        return g_psDescriptor;
    else
        return NULL;
}

Windowsでのコンパイルは以下のコマンドを使った。
gcc -shared -o namagi_delay.dll namagi_delay.c -ID


ビット演算の説明
<<= は左シフト代入演算子といって、2進数の数を左へシフトしていくもの。もともとあった値はそのままシフトされるが、ない部分には0が入る。 m_lBufferSize(unsigned long)は 4バイト、10進数値0~4294967295まで扱う。 まず1を代入し、SampleRateの44100を超えるまでwhileでビット演算をする。 一回目は2進数の1が左にシフトし10となり10進数で2となる。 3回目は2進数で100となり、10進数で4となる。 このように2のべき乗で、2,4,8,16,31,64,128,256・・・と増えていく。 本来SampleRateの44100まであればよいのだが、これは2のべき乗ではないので、その上の65536が44100を内包した2のべき乗となり、今回使える数となる。

ディレイ処理の要はビット演算を利用したリングバッファ。 下記の読み出し部分。バッファのサイズは1秒の場合、65536となる。
pfBuffer[(i + lBufferReadOffset) & lBufferSizeMinusOne];
&はビット演算子で2進数のとき両辺が1のときのみ1になる計算。
たとえば、1001 & 1110 = 1000 という具合。両辺が1なのは4桁目だけなのでこうなる。
1回目の処理としては実数を入れるとこうなる。
0+21436(101001110111100) & 65535(1111111111111111) = 21436
つまり 21436個目から読み出す。
さらに1秒後のi=44100のときは、
44100+21436=65536
65536(10000000000000000) & 65535(1111111111111111) = 0
となり、バッファの1番目から読みだす。 このようにビット演算を使うことでバッファを回すことが可能。
ちなみに書き込みは以下の部分
pfBuffer[(i +lBufferReadOffset) & lBufferSizeMinusOne];
1回目の処理は
(0+0) & 65535(1111111111111111) = 0
となり、0個目から書き込む。



sound programming 目次はこちら