2015/07/29

LADSPA IIR LPF Butterworth 1pole

処理が軽くて便利なIIRのLPF(ローパスフィルター)をLADSPAで作成してみる。 IIRとは、無限インパルス応答をもつ再帰型フィルタで、アナログ回路に起源を持つ。

まずは、もっともタップ数が少ない1poleのutterworthを試す。 3年以上前にJavaで作っているので、その焼き直し。

上記式から1poleのプロトタイプを計算

LPFを作りたいので、上記式 s に次式を代入する。
LPF: ((z-1)/(z+1)) / (2πfc)

fcの中はこんなかんじ。ωcはカットオフ周波数で、fsはサンプリング周波数。

ブロック図は下図を採用。



LPF 1pole Butterworth 係数計算

まずは係数を求める部分だけテストしてみた。これはLADSPAではなく、ターミナル上で係数を確認するためのプログラム。
#include <stdio.h>
#include <math.h>

int main(void){
  float m_fSampleRate = 44100; /* サンプリング周波数 */
  float fwc = 1000; /* カットオフ周波数 Hz */
  
  /* 配列の作成 */
  float a[2];
  float b[2];
  
  /* 係数を求める */
  float fc = tan(M_PI * fwc / m_fSampleRate) / (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];
  a[0]= 1.0; /* 初期値に戻す 実際は使わない */
  
  /* 各係数を出力 */
  printf("a[0]=%f\na[1]=%f\nb[0]=%f\nb[1]=%f",a[0],a[1],b[0],b[1]);
  
  return 0;
}

a.exe を実行

上記プログラムをコンパイルして、実行すると以下のような結果がでる。LPF Butterworth 1poleの係数がちゃんと出ている。
a[0]=1.000000
a[1]=0.866788
b[0]=0.066606
b[1]=0.066606

予備実験は終了。後はLADSPAで組んで実際の音に適用してみる。

LADSPA LPF 1pole Butterworth ソースコード

相変わらず、よく理解しないまま作ってしまう。LADSPAの仕様をもう少し調べた方がよいのだが、とりあえず動かすことを優先してしまう。
/* namagi_lpf_1p.c 2015.07.29
 * LPF Butterworth 1pole
 * compile windows
 * gcc -shared -o namagi_lpf_1p.dll namagi_lpf_1p.c -ID 
 * 
 * compile Ubuntu
 * gcc -fPIC -DPIC -shared -nostartfiles
   -o namagi_lpf_1p.so namagi_lpf_1p.c
 */
/**********************************************************/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
/**********************************************************/
/*LADSPAのヘッダファイルを読み込む*/
#include "ladspa.h"
/**********************************************************/
/* マクロ コントロールと出力 */
#define LPF_CONTROL 0
#define LPF_INPUT1  1
#define LPF_OUTPUT1 2
/**********************************************************/
#ifdef WIN32
   int bIsFirstTime = 1;
   void _init();
   #define _WINDOWS_DLL_EXPORT_ __declspec(dllexport)
#else
   #define _WINDOWS_DLL_EXPORT_ 
#endif
/**********************************************************/
/* 構造体で入出力とコントロールを定義 */
typedef struct {
  LADSPA_Data   m_fSampleRate; /* LADSPA_Data は float */
  LADSPA_Data * m_pfControlValue; /* コントロール */
  LADSPA_Data * m_pfInputBuffer1; /* mono 入力 */
  LADSPA_Data * m_pfOutputBuffer1; /* mono 出力 */
} Lpf;
/**********************************************************/
/* インスタンス化 */
LADSPA_Handle instantiatelpf(const LADSPA_Descriptor * Descriptor,
       unsigned long SampleRate) {
  Lpf * pssLpf;
  pssLpf =  (Lpf *)malloc(sizeof(Lpf));
  pssLpf -> m_fSampleRate = (LADSPA_Data)SampleRate;
  return; //malloc(sizeof(Lpf));
  }
/**********************************************************/
/* portに接続 コントロールと入出力 */
void connectPortTolpf(LADSPA_Handle Instance,
         unsigned long Port,
         LADSPA_Data * DataLocation) 
{
  Lpf * pslpf;
  pslpf = (Lpf *)Instance;

  switch (Port) {
  case LPF_CONTROL:
    pslpf->m_pfControlValue = DataLocation;
    break;
  case LPF_INPUT1:
    pslpf->m_pfInputBuffer1 = DataLocation;
    break;
  case LPF_OUTPUT1:
    pslpf->m_pfOutputBuffer1 = DataLocation;
    break;
  }
}
/**********************************************************/
/* LPF Butterworth 1pole 処理 */
void runlpf(LADSPA_Handle Instance, unsigned long SampleCount) 
{ 
  LADSPA_Data * pfInput;
  LADSPA_Data * pfOutput;
  LADSPA_Data fwc; /* カットオフ周波数 Hz */
  unsigned long lSampleIndex;
  Lpf * pslpf;
  pslpf = (Lpf *)Instance;
  
  fwc = *(pslpf    -> m_pfControlValue);
  pfInput = pslpf  -> m_pfInputBuffer1;
  pfOutput = pslpf -> m_pfOutputBuffer1;
  
  float Z = 0; /* tap */
  
  /* 配列の作成 */
  float a[2];
  float b[2];
  
  /* 各係数を求める */
  float fc = tan(M_PI * fwc / pslpf -> m_fSampleRate) / (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];
  
  /* 入出力 */
  for (lSampleIndex = 0; lSampleIndex < SampleCount; lSampleIndex++) {
    *(pfOutput) = (*(pfInput)+(Z * a[1])) * b[0] + (Z  * b[1]);
    Z = *(pfInput)+(Z * a[1]);
    *(pfOutput++);
    *(pfInput++);
  }
}
/**********************************************************/
/* 開放 */
void cleanuplpf(LADSPA_Handle Instance) {
  free(Instance);
}
/**********************************************************/
/*  */
LADSPA_Descriptor * g_psMonoDescriptor = NULL;
/**********************************************************/
/* 最初に呼び出される部分 インターフェイスや名前などの記述 */
void _init() 
{
  char ** pcPortNames;
  int ports_num = 3;
  LADSPA_PortDescriptor * piPortDescriptors;
  LADSPA_PortRangeHint  * psPortRangeHints;
  
    g_psMonoDescriptor
      = (LADSPA_Descriptor *)malloc(sizeof(LADSPA_Descriptor));
    g_psMonoDescriptor -> UniqueID
      = 1048;
    g_psMonoDescriptor -> Label
      = strdup("lpf");
    g_psMonoDescriptor -> Properties
      = LADSPA_PROPERTY_HARD_RT_CAPABLE;
    g_psMonoDescriptor -> Name 
      = strdup("Namagi: LPF ver.150729");
    g_psMonoDescriptor -> Maker
      = strdup("Namagi Products");
    g_psMonoDescriptor -> Copyright
      = strdup("None");
    g_psMonoDescriptor -> PortCount
      = ports_num;
    piPortDescriptors
      = (LADSPA_PortDescriptor *)calloc(ports_num, 
        sizeof(LADSPA_PortDescriptor));
    g_psMonoDescriptor->PortDescriptors
      = (const LADSPA_PortDescriptor *)piPortDescriptors;
    piPortDescriptors[LPF_CONTROL]
      = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
    piPortDescriptors[LPF_INPUT1]
      = LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO;
    piPortDescriptors[LPF_OUTPUT1]
      = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO;
    pcPortNames
      = (char **)calloc(ports_num, sizeof(char *));
    g_psMonoDescriptor->PortNames 
      = (const char **)pcPortNames;
    pcPortNames[LPF_CONTROL]
      = strdup("wc(Hz)");
    pcPortNames[LPF_INPUT1]
      = strdup("Input");
    pcPortNames[LPF_OUTPUT1]
      = strdup("Output");
    psPortRangeHints
      = ((LADSPA_PortRangeHint *)
        calloc(ports_num, sizeof(LADSPA_PortRangeHint)));
    g_psMonoDescriptor->PortRangeHints
      = (const LADSPA_PortRangeHint *)psPortRangeHints;
    psPortRangeHints[LPF_CONTROL].HintDescriptor
      = (LADSPA_HINT_BOUNDED_BELOW 
        | LADSPA_HINT_LOGARITHMIC | LADSPA_HINT_DEFAULT_1);

    psPortRangeHints[LPF_CONTROL].HintDescriptor
      = (LADSPA_HINT_BOUNDED_BELOW | 
      LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_LOW);
    psPortRangeHints[LPF_CONTROL].LowerBound 
      = 0;
    psPortRangeHints[LPF_CONTROL].UpperBound
      = 20000;
    psPortRangeHints[LPF_INPUT1].HintDescriptor
      = 0;
    psPortRangeHints[LPF_OUTPUT1].HintDescriptor
      = 0;
    g_psMonoDescriptor -> instantiate 
      = instantiatelpf;
    g_psMonoDescriptor -> connect_port 
      = connectPortTolpf;
    g_psMonoDescriptor -> activate
      = NULL;
    g_psMonoDescriptor -> run
      = runlpf;
    g_psMonoDescriptor -> run_adding
      = NULL;
    g_psMonoDescriptor -> set_run_adding_gain
      = NULL;
    g_psMonoDescriptor -> deactivate
      = NULL;
    g_psMonoDescriptor -> cleanup
      = cleanuplpf;
}
/**********************************************************/
/* 開放 インターフェイスまわり */
void deleteDescriptor(LADSPA_Descriptor * psDescriptor) {
  unsigned long lIndex;
  if (psDescriptor) {
    free((char *)psDescriptor->Label);
    free((char *)psDescriptor->Name);
    free((char *)psDescriptor->Maker);
    free((char *)psDescriptor->Copyright);
    free((LADSPA_PortDescriptor *)psDescriptor->PortDescriptors);
    for (lIndex = 0; lIndex < psDescriptor->PortCount; lIndex++)
      free((char *)(psDescriptor->PortNames[lIndex]));
    free((char **)psDescriptor->PortNames);
    free((LADSPA_PortRangeHint *)psDescriptor->PortRangeHints);
    free(psDescriptor);
  }
}
/**********************************************************/
void _fini() {
  deleteDescriptor(g_psMonoDescriptor);
}
/**********************************************************/
_WINDOWS_DLL_EXPORT_
const LADSPA_Descriptor * ladspa_descriptor(unsigned long Index) {
#ifdef WIN32
  if (bIsFirstTime) {
  _init(); 
  bIsFirstTime = 0;
  }
#endif
  if (Index == 0)
    return g_psMonoDescriptor;
  else
    return NULL;
}

上記をコンパイルしてAudacityで起動すると以下のようなインターフェイス。 入力はカットオフ周波数だけだが、実験的に0~20kHzまで扱うようにした。0Hzで実行すると出力信号は0になる。

ホワイトノイズに適用してみた。上段がホワイトノイズで、下段がLPFを適用した状態。カットオフ周波数は1000Hz。

周波数スペクトルを見ると、LPFによって、1000Hz以上が削れているのがわかる。 カットオフ周波数で-4~5dB程度になっている。

お手軽LADSPAであるが、ソースコードはダラダラしやすい。またインターフェイスまわりは、ホスト側が提供することもあって、自由度はあまりない。Audacityでは、パラメーターを増やすとウィンドウサイズばかり大きくなって扱いにくくなってしまう。コンパクトで扱いやすいインターフェイスを作ることは今のところ出来そうもない。
そんなこともあって、ユーザーインターフェイスがVSTのように自由に作れるLV2というLADSPAの後継規格もある。Audacityでもver2.0.6から利用できるようになった。 ただGUIの開発のためにはGTKが必須となって、開発環境がそれなりに巨大になる。コンパイルもMakefileを使うなど、いろいろ大げさな印象。プラットフォームによって開発手続きが違うのも問題。 だったらLADSPAでいいや・・・

sound programming 目次はこちら