LADSPA Stereo Delay

久しぶりにLADSPAでエフェクトを作ってみる。 前回作ったのは夏なので、早くも3ヶ月経ってしまった。

今回はステレオディレイで、割と実用的なものにしてみた。 ブロック図にすると以下のような内容。以前Javaで作ったものよりも簡易的にしてしまった。

特徴としては、ディレイをもう一方のチャンネルにも出力できるというもの。 定位のコントロールとかが出来て便利。 夏に作ったIIRのLPF Butterworth 1pole も実装。 ディレイタイムは1サンプルから可能で1秒までとした。1秒以上のロングは必要ないでしょう。 モノラルに適用するときは左チャンネルの設定が有効になる。


サンプル音

まずはドライのモノラル音。

次ステレオディレイを使った音。空間演出が出来ているのがわかると思う。


LADSPA Stereo Delay ソースコード

ソースは、不要なものは、なるべく省いてみた。それでもダラダラとして長くなってしまった。 インターフェイス関係があるので仕方ない。 またLADSPAの仕様からちょっと外れた書き方もしている。
/* namagi_delay_stereo.c
2015.11.14

compile windows
gcc -shared -o namagi_delay_stereo.dll namagi_delay_stereo.c -ID

compile Ubuntu
gcc -fPIC -DPIC -shared -nostartfiles
 -o namagi_delay_stereo.so namagi_delay_stereo.c
*/
/**********************************************************/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "ladspa.h"
/**********************************************************/
#define INPUT           0
#define OUTPUT          1
#define INPUT_R         2
#define OUTPUT_R        3

#define DELAY_TIME      4
#define WET             5
#define WET2            6
#define FEEDBACK        7
#define LPF             8

#define DELAY_TIME_R    9
#define WET_R           10
#define WET2_R          11
#define FEEDBACK_R      12
#define LPF_R           13

#define DRYCHECK        14
#define DRY             15

/**********************************************************/
#ifdef WIN32
    int bIsFirstTime = 1; 
    void _init();
    #define _WINDOWS_DLL_EXPORT_ __declspec(dllexport)
#else
    #define _WINDOWS_DLL_EXPORT_ 
#endif
/**********************************************************/
typedef struct {
    float *m_pfInput;
    float *m_pfOutput;
    float *m_pfInputR;
    float *m_pfOutputR;

    float *m_pfDelay;
    float *m_pfWet;
    float *m_pfWet2;
    float *m_pfFeedback;
    float *m_pfLPF;
    float *m_pfBuffer;
    unsigned long m_lWritePointer;
    
    float *m_pfDelayR;
    float *m_pfWetR;
    float *m_pfWet2R;
    float *m_pfFeedbackR;
    float *m_pfLPFR;
    float *m_pfBufferR;
    unsigned long m_lWritePointerR;

    unsigned long  m_lSampleRate;
    unsigned long m_lBufferSize;
    unsigned long m_lBufferSizeR;
    float *m_pfDryCheck;
    float *m_pfDry;
} S;
/**********************************************************/
LADSPA_Handle 
instantiateDelay(const LADSPA_Descriptor *Descriptor, unsigned long SampleRate){

    S *psS;
    psS = (S *)malloc(sizeof(S));
    if (psS == NULL) return NULL;
    
    /*サンプリング周波数*/
    psS -> m_lSampleRate = SampleRate;
    
    /* L */
    psS -> m_lBufferSize = 1;
    while (psS -> m_lBufferSize < SampleRate){
        psS -> m_lBufferSize <<= 1;
    }
    psS -> m_pfBuffer = (float *)calloc(psS -> m_lBufferSize, sizeof(float));
    if (psS -> m_pfBuffer == NULL) {
        free(psS);
        return NULL;
    }
    /* R */
    psS -> m_lBufferSizeR = 1;
    while (psS -> m_lBufferSizeR < SampleRate){
        psS -> m_lBufferSizeR <<= 1;
    }
    psS -> m_pfBufferR = (float *)calloc(psS -> m_lBufferSizeR, sizeof(float));
    if (psS -> m_pfBufferR == NULL) {
        free(psS);
        return NULL;
    }
    psS -> m_lWritePointer = 0;
    psS -> m_lWritePointerR = 0;
    return psS;
}
/***********************************************************/
void activateDelay(LADSPA_Handle Instance){
    S *psS;
    psS = (S *)Instance;
    /* バッファを0で埋める 初期化 */
    memset(psS -> m_pfBuffer, 0, 
    sizeof(float) *psS -> m_lBufferSize);
    memset(psS -> m_pfBufferR, 0,
    sizeof(float) *psS -> m_lBufferSizeR);
}
/**********************************************************/
void connectPortToDelay(LADSPA_Handle Instance,
    unsigned long Port,float *DataLocation){
    S *psS;
    psS = (S *)Instance;

    switch (Port) {
    case INPUT:
        psS -> m_pfInput = DataLocation;
    break;
    case OUTPUT :
        psS -> m_pfOutput = DataLocation;
    break;
    case INPUT_R:
        psS -> m_pfInputR = DataLocation;
    break;
    case OUTPUT_R :
        psS -> m_pfOutputR = DataLocation;
    break;

    case DELAY_TIME:
        psS -> m_pfDelay = DataLocation;
    break; 
    case WET:
        psS -> m_pfWet = DataLocation;
    break;
    case WET2:
        psS -> m_pfWet2 = DataLocation;
    break;
    case FEEDBACK:
        psS -> m_pfFeedback = DataLocation;
    break;
    case LPF:
        psS -> m_pfLPF = DataLocation;
    break;
        
    case DELAY_TIME_R:
        psS -> m_pfDelayR = DataLocation;
    break;  
    case WET_R:
        psS -> m_pfWetR = DataLocation;
    break;
    case WET2_R:
        psS -> m_pfWet2R = DataLocation;
    break;
    case FEEDBACK_R:
        psS -> m_pfFeedbackR = DataLocation;
    break;
    case LPF_R:
        psS -> m_pfLPFR = DataLocation;
    break;
    
    case DRYCHECK:
        psS -> m_pfDryCheck = DataLocation;
    break;
    case DRY:
        psS -> m_pfDry = DataLocation;
    break;
    }
}
/**********************************************************/
void runDelay(LADSPA_Handle Instance,
     unsigned long SampleCount){
    S *psS;
    psS = (S*) Instance;
    
    float *in = psS  -> m_pfInput;
    float *out = psS -> m_pfOutput;
    float *inR = psS  -> m_pfInputR;
    float *outR = psS -> m_pfOutputR;
    float *pfBuffer = psS -> m_pfBuffer;
    float *pfBufferR = psS -> m_pfBufferR;

    float fInputSample;
    float fInputSampleR;
    float fInputL;
    float fInputR;
    float fWet = *(psS -> m_pfWet);  /* wet */
    float fWetR = *(psS -> m_pfWetR);
    float fWet2 = *(psS -> m_pfWet2);  /* wet2 */
    float fWet2R = *(psS -> m_pfWet2R);
    float ffb = *(psS -> m_pfFeedback);/* feedback */
    float ffbR = *(psS -> m_pfFeedbackR);
    
    float fDryCheck = *(psS -> m_pfDryCheck);
    float fDry = *(psS -> m_pfDry);
    
    unsigned long lBufferSizeMinusOne
     = psS -> m_lBufferSize - 1;
    unsigned long lBufferSizeMinusOneR
     = psS -> m_lBufferSizeR - 1;

    unsigned long lDelay
    = *(psS -> m_pfDelay) * psS -> m_lSampleRate / 1000;
    unsigned long lDelayR
    = *(psS -> m_pfDelayR) * psS -> m_lSampleRate / 1000;
    
    unsigned long lBufferWriteOffset = psS -> m_lWritePointer;
    unsigned long lBufferWriteOffsetR = psS -> m_lWritePointerR;
    
    unsigned long lBufferReadOffset
     = lBufferWriteOffset + psS -> m_lBufferSize - lDelay;
    unsigned long lBufferReadOffsetR
     = lBufferWriteOffsetR + psS -> m_lBufferSizeR - lDelayR;

    unsigned long i;
    
    float drydB = pow(10,fDry/20.0);
    float wetdB = pow(10,fWet/20.0);
    float wet2dB = pow(10,fWet2/20.0);
    float wetdBR = pow(10,fWetR/20.0);
    float wet2dBR = pow(10,fWet2R/20.0);
    
    /* LPF Butterworth 1pole */
    float fwc = *(psS -> m_pfLPF); /* カットオフ周波数 Hz */
    float fwcR = *(psS -> m_pfLPFR);
    float Z = 0; /* tap */
    float ZR = 0;
    /* 配列の作成 */
    float a[2];
    float b[2];
    float aR[2];
    float bR[2];
    /* 各係数を求める */
    float fc = tan(M_PI * fwc / psS -> m_lSampleRate) / (2 * M_PI); 
    float A = 2 * M_PI * fc;
    a[0]= A+1.0; /* a0を有効に */
    a[1]= -1*(A-1.0)/a[0]; /*フィードバック */
    b[0]= A/a[0];
    b[1]= A/a[0];
    float fcR = tan(M_PI * fwcR / psS -> m_lSampleRate) / (2 * M_PI); 
    float AR = 2 * M_PI * fcR;
    aR[0]= AR+1.0; /* a0を有効に */
    aR[1]= -1*(AR-1.0)/aR[0]; /*フィードバック */
    bR[0]= AR/aR[0];
    bR[1]= AR/aR[0];
    
    /*ディレイ処理*/
    for (i=0; i < SampleCount; i++){
        fInputSample = *(in++);
        fInputSampleR = *(inR++);
        fInputL = (pfBuffer[(i + lBufferReadOffset)
                    & lBufferSizeMinusOne] + (Z * a[1]))
                    * b[0] + (Z  * b[1]);
        fInputR = (pfBufferR[(i + lBufferReadOffsetR)
                    & lBufferSizeMinusOneR] + (ZR * aR[1]))
                    * bR[0] + (ZR  * bR[1]);
        if(fDryCheck == 1){
            *(out++) =  
               fInputL * wetdB + fInputR * wet2dBR + fInputSample * drydB;
            *(outR++) =
               fInputR * wetdBR + fInputL * wet2dB + fInputSampleR * drydB;
        }else{
            *(out++) =  fInputL * wetdB + fInputR * wet2dBR;
            *(outR++) = fInputR * wetdBR + fInputL * wet2dB;
        }
        Z = pfBuffer[(i + lBufferReadOffset) & lBufferSizeMinusOne] + (Z * a[1]);
        ZR = pfBufferR[(i + lBufferReadOffsetR) & lBufferSizeMinusOneR] + (ZR * aR[1]);
        
        pfBuffer[(i + lBufferWriteOffset) & lBufferSizeMinusOne]
        = fInputSample 
        + ffb * pfBuffer[(i +lBufferReadOffset) & lBufferSizeMinusOne];
        
        pfBufferR[(i + lBufferWriteOffsetR) & lBufferSizeMinusOneR]
        = fInputSampleR 
        + ffbR * pfBufferR[(i +lBufferReadOffsetR) & lBufferSizeMinusOneR];
    }
    psS -> m_lWritePointer 
    = ((psS -> m_lWritePointer + SampleCount) & lBufferSizeMinusOne);
    
    psS -> m_lWritePointerR 
    = ((psS -> m_lWritePointerR + SampleCount) & lBufferSizeMinusOneR);
}
/**********************************************************/
void cleanupDelay(LADSPA_Handle Instance){
    S * psS;
    psS = (S *)Instance;
    free(psS);
}
/**********************************************************/
LADSPA_Descriptor *g_psDescriptor = NULL;
/**********************************************************/
void _init(){
    char ** pcPortNames;
    LADSPA_PortDescriptor *piPortDescriptors;
    LADSPA_PortRangeHint *psPortRangeHints;
    int ports = 16;/* ★ポート数 4箇所書き換えミスを防ぐ */
    g_psDescriptor = (LADSPA_Descriptor *)malloc(sizeof(LADSPA_Descriptor));
    if (g_psDescriptor) {
        g_psDescriptor->UniqueID
        = 4;
        g_psDescriptor->Label
        = strdup("delay stereo");
        g_psDescriptor->Properties
        = LADSPA_PROPERTY_HARD_RT_CAPABLE;
        g_psDescriptor->Name
        = strdup("Namagi: Delay Stereo ver.151114");
        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[INPUT]
        =  LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO;
        piPortDescriptors[OUTPUT]
        = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO;
        piPortDescriptors[INPUT_R]
        =  LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO;
        piPortDescriptors[OUTPUT_R]
        = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO;
        
        piPortDescriptors[DELAY_TIME]
        = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
        piPortDescriptors[WET]
        = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
        piPortDescriptors[WET2]
        = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
        piPortDescriptors[FEEDBACK]
        = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
        piPortDescriptors[LPF] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
        
        piPortDescriptors[DELAY_TIME_R]
        = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
        piPortDescriptors[WET_R]
        = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
        piPortDescriptors[WET2_R]
        = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
        piPortDescriptors[FEEDBACK_R]
        = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
        piPortDescriptors[LPF_R]
        = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
        
        piPortDescriptors[DRYCHECK]
        = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
        piPortDescriptors[DRY]
        = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
        
        pcPortNames = (char **)calloc(ports, sizeof(char *));
        g_psDescriptor -> PortNames = (const char **)pcPortNames;

        pcPortNames[INPUT] = strdup("input_L");
        pcPortNames[OUTPUT] = strdup("output_L");
        pcPortNames[INPUT_R] = strdup("input_R");
        pcPortNames[OUTPUT_R] = strdup("output_R");
        
        pcPortNames[DELAY_TIME] = strdup("L delay msec.");
        pcPortNames[WET] =  strdup(" wet dB");
        pcPortNames[WET2] =  strdup(" wet to R dB");
        pcPortNames[FEEDBACK] = strdup(" feedback");
        pcPortNames[LPF] = strdup(" LPF Hz");
        
        pcPortNames[DELAY_TIME_R] = strdup("R delay msec.");
        pcPortNames[WET_R] =  strdup(" wet dB");
        pcPortNames[WET2_R] =  strdup(" wet to L dB");
        pcPortNames[FEEDBACK_R] = strdup(" feedback");
        pcPortNames[LPF_R] = strdup(" LPF Hz");
        
        pcPortNames[DRYCHECK] = strdup("dry on/off");
        pcPortNames[DRY] = strdup("dry dB");
        
        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.05;
        psPortRangeHints[DELAY_TIME].UpperBound = 1000.0; 

        psPortRangeHints[WET].HintDescriptor
        = (LADSPA_HINT_BOUNDED_BELOW
        | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_HIGH);
        psPortRangeHints[WET].LowerBound = -120;
        psPortRangeHints[WET].UpperBound = 0;

        psPortRangeHints[WET2].HintDescriptor
        = (LADSPA_HINT_BOUNDED_BELOW
        | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_HIGH);
        psPortRangeHints[WET2].LowerBound = -120;
        psPortRangeHints[WET2].UpperBound = 0;
    
        psPortRangeHints[FEEDBACK].HintDescriptor
        = (LADSPA_HINT_BOUNDED_BELOW
        | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_HIGH);
        psPortRangeHints[FEEDBACK].LowerBound = -1;
        psPortRangeHints[FEEDBACK].UpperBound = 1;
        
        psPortRangeHints[LPF].HintDescriptor
        = (LADSPA_HINT_BOUNDED_BELOW
        | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_HIGH);
        psPortRangeHints[LPF].LowerBound = 10;
        psPortRangeHints[LPF].UpperBound = 20000;
        
        psPortRangeHints[DELAY_TIME_R].HintDescriptor 
        = (LADSPA_HINT_BOUNDED_BELOW
        | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_LOW);
        psPortRangeHints[DELAY_TIME_R].LowerBound = 0.05;
        psPortRangeHints[DELAY_TIME_R].UpperBound = 1000.0; 

        psPortRangeHints[WET_R].HintDescriptor
        = (LADSPA_HINT_BOUNDED_BELOW
        | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_HIGH);
        psPortRangeHints[WET_R].LowerBound = -120;
        psPortRangeHints[WET_R].UpperBound = 0;
    
        psPortRangeHints[WET2_R].HintDescriptor
        = (LADSPA_HINT_BOUNDED_BELOW
        | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_HIGH);
        psPortRangeHints[WET2_R].LowerBound = -120;
        psPortRangeHints[WET2_R].UpperBound = 0;

        psPortRangeHints[FEEDBACK_R].HintDescriptor
        = (LADSPA_HINT_BOUNDED_BELOW
        | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_HIGH);
        psPortRangeHints[FEEDBACK_R].LowerBound = -1;
        psPortRangeHints[FEEDBACK_R].UpperBound = 1;
        
        psPortRangeHints[LPF_R].HintDescriptor
        = (LADSPA_HINT_BOUNDED_BELOW
        | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_HIGH);
        psPortRangeHints[LPF_R].LowerBound = 10;
        psPortRangeHints[LPF_R].UpperBound = 20000;
        
        psPortRangeHints[DRYCHECK].HintDescriptor
        = (LADSPA_HINT_TOGGLED | LADSPA_HINT_DEFAULT_1);/* check = 1 */
    
        psPortRangeHints[DRY].HintDescriptor
        = (LADSPA_HINT_BOUNDED_BELOW
        |LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_0);
        psPortRangeHints[DRY].LowerBound = -120;
        psPortRangeHints[DRY].UpperBound = 0;

        psPortRangeHints[INPUT].HintDescriptor = 0;
        psPortRangeHints[OUTPUT].HintDescriptor = 0;

        psPortRangeHints[INPUT_R].HintDescriptor = 0;
        psPortRangeHints[OUTPUT_R].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;
}


sound programming 目次