2013/01/25

Reverbの自作1 comb filter

リバーブを作ってみようかな

今までJavaやCなどでいくつかエフェクターを作っているが、リバーブはまだ作ったことがない。その理由はリバーブは複数のエフェクトで構成されていて、作る人によって実現方法はいくらでもあり得るから。こういうつかみ所がないエフェクターは狙いが明確でないと作る意味が分からなくなってしまう。複合的な要素が多いので、実験的に遊ぶには規模が大きくなってしまう問題もある。今回作ってみようかと思ったのは、やはり録音にはリバーブは欠かせないと思えたから。どうせなら基礎的なところから積み上げてみようかと思っている。まずは原点ともいえる Manfred Robert Schroeder が1962年に発表したアルゴリズムからスタート。そこから数ヶ月ぐらいかけてオリジナルをノロノロ作っていこうと思う。初期の実験段階ではLADSPAを使うが、最終的にはC言語による完全オリジナルにしようかと思っている。現在参考にしているのはARIのサイト。日本語でリバーブの原理について丁寧に書かれている。
http://www.ari-web.com


comb filter (コムフィルタ)

まずは基礎の基礎であるcomb filterの確認からしてみる。comb filterもいくつかタイプがあり、それぞれブロック図や効果も違うのだが、まずはARIにあるブロック図を採用してみる。中身はフィードバックが永久に巡回するIIR型で、ドライなし、フィードバック(gainあり)付きの単純なディレイ。

適用すると周波数特性がくし形になるので、comb filterと呼ばれている。LADSPAで作ったディレイを改造してARIと同じ仕様にしてAudacityで使えるプラグインにしてみる。

設定はディレイ100msecにして、フィードバック0.87にしてみた。デルタ関数にcomb filterをかけてインパルス応答を確認。パラメーターは上と同じ。Dryはなく、Wetだけが出力されるので、オリジナルのデルタ関数が100msec後にそのまま出力され、その後は100msecごとに0.87倍になって出力されている。

ホワイトノイズにcomb filterをかけてみるとギザギザの特性になるのを確認。ディレイタイムが短いとはっきりと出てくるので、設定はディレイ5msec、フィードバック0.87にしてみた。ディレイ長くしていくとギザギザは薄れていく。

上は周波数軸が対数表示だが、これを下のようにリニア表示すると完全に等間隔にギザギザになっている。音は特定の周波数が強調されるために金属的な印象がある。

実際にギターの音にcombフィルタをかけてみる。はじめにギター5弦の開放(A2 110Hz)をボーンと鳴らしたオリジナル音。次にcombフィルタを適用した音が聴ける。 設定は、リバーブを意識してかなり短めのディレイ0.15msec、フィードバック0.77。音量は後で小さく調整して、オリジナルに近づけている。明瞭度は落ちるが、密度があがった感じになる。



残響時間の計算

次に残響時間を次式で計算してみる。残響時間は-60dB(100万分の1)になるまでと定義されているようだ。

タウは遅延時間(ディレイ)
gはフィードバック
Trは残響時間
実際は残響時間から数値を決めたいので、変形した次式を使う。

下図はデルタ関数に下記設定を適用したもの
ディレイ 0.1sec
フィードバック 0.87
なので、計算すると
残響時間は 4.96sec になる。
実際の波形、聴いた感じも4.96secまで音が伸びているという印象で、計算値と同等とみてよい。



LADSPA comb filter

LADSPAのソースは以下の通り。ディレイの改造なので、無駄なものがいろいろ入っている可能性大。
/* namagi_comb.c 2013.01.25
compile:
gcc -shared -o namagi_comb.dll namagi_comb.c -ID
*/
/***********************************************************/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "ladspa.h"
#define MAX_DELAY 1000
#define SDL_DELAY        0
#define SDL_FEEDBACK     1
#define SDL_INPUT        2
#define SDL_OUTPUT       3
/***********************************************************/
#ifdef WIN32
int bIsFirstTime = 1; 
void _init();
#endif
#ifdef WIN32
 #define _WINDOWS_DLL_EXPORT_ __declspec(dllexport)
#else
    #define _WINDOWS_DLL_EXPORT_ 
#endif
/***********************************************************/
#define LIMIT_BETWEEN_0_AND_MAX_DELAY(x)  \
(((x) < 0) ? 0 : (((x) > MAX_DELAY) ? MAX_DELAY : (x)))
/***********************************************************/
typedef struct {
  LADSPA_Data m_fSampleRate; 
  LADSPA_Data *m_pfBuffer;
  unsigned long m_lBufferSize;
  unsigned long m_lWritePointer;
  LADSPA_Data *m_pfDelay;
  LADSPA_Data *m_pfFeedback;
  LADSPA_Data *m_pfInput;
  LADSPA_Data *m_pfOutput;
} SimpleDelayLine;
/***********************************************************/
LADSPA_Handle 
instantiateSimpleDelayLine(const LADSPA_Descriptor
        * Descriptor, 
        unsigned long SampleRate){
  unsigned long lMinimumBufferSize;
  SimpleDelayLine * psDelayLine;
  psDelayLine = 
     (SimpleDelayLine *)malloc(sizeof(SimpleDelayLine));
  if (psDelayLine == NULL) return NULL;
  psDelayLine -> m_fSampleRate = (LADSPA_Data)SampleRate;
  lMinimumBufferSize = (LADSPA_Data)SampleRate;
  psDelayLine -> m_lBufferSize = 1;
  while (psDelayLine -> m_lBufferSize < lMinimumBufferSize)
    psDelayLine -> m_lBufferSize <<= 1;
  psDelayLine -> m_pfBuffer = 
    (LADSPA_Data *)calloc(psDelayLine -> 
        m_lBufferSize, sizeof(LADSPA_Data));
  if (psDelayLine -> m_pfBuffer == NULL) {
    free(psDelayLine);
    return NULL;
  }
  psDelayLine -> m_lWritePointer = 0;
  return psDelayLine;
}
/***********************************************************/
void activateSimpleDelayLine(LADSPA_Handle Instance) {
  SimpleDelayLine *psSimpleDelayLine;
  psSimpleDelayLine = (SimpleDelayLine *)Instance;
  memset(psSimpleDelayLine -> m_pfBuffer, 
     0, sizeof(LADSPA_Data)
     *psSimpleDelayLine -> m_lBufferSize);
}
/***********************************************************/
void connectPortToSimpleDelayLine(LADSPA_Handle Instance,
        unsigned long Port,LADSPA_Data * DataLocation){
  SimpleDelayLine * psSimpleDelayLine;
  psSimpleDelayLine = (SimpleDelayLine *)Instance;
  
  switch (Port) {
  case SDL_DELAY:
    psSimpleDelayLine -> m_pfDelay = DataLocation;
    break;  
  case SDL_FEEDBACK:
    psSimpleDelayLine -> m_pfFeedback = DataLocation;
    break;
  case SDL_INPUT:
    psSimpleDelayLine -> m_pfInput = DataLocation;
    break;
  case SDL_OUTPUT:
    psSimpleDelayLine -> m_pfOutput = DataLocation;
    break;
  }
}
/***********************************************************/
void runSimpleDelayLine(LADSPA_Handle Instance, 
     unsigned long SampleCount){
  LADSPA_Data *pfBuffer;
  LADSPA_Data *pfInput;
  LADSPA_Data *pfOutput;
  LADSPA_Data fInputSample;
  LADSPA_Data fOutputSample;
  LADSPA_Data fFeedback;
  SimpleDelayLine *psSimpleDelayLine;
  unsigned long lBufferReadOffset;
  unsigned long lBufferSizeMinusOne;
  unsigned long lBufferWriteOffset;
  unsigned long lDelay;
  unsigned long lSampleIndex;
  psSimpleDelayLine =
     (SimpleDelayLine*) Instance;
  lBufferSizeMinusOne =
     psSimpleDelayLine -> m_lBufferSize - 1;
  lDelay = (unsigned long)
  (LIMIT_BETWEEN_0_AND_MAX_DELAY(*(psSimpleDelayLine ->
     m_pfDelay)) 
     *psSimpleDelayLine -> m_fSampleRate);
  pfInput = psSimpleDelayLine -> m_pfInput;
  pfOutput = psSimpleDelayLine -> m_pfOutput;
  pfBuffer = psSimpleDelayLine -> m_pfBuffer;

  lBufferWriteOffset = 
    psSimpleDelayLine -> m_lWritePointer;
  lBufferReadOffset = 
    lBufferWriteOffset + psSimpleDelayLine -> 
    m_lBufferSize - (float)lDelay/1000;
  fFeedback = *(psSimpleDelayLine -> m_pfFeedback);
  for (lSampleIndex = 0; 
      lSampleIndex < SampleCount; 
      lSampleIndex++){
    fInputSample = *(pfInput++);
   *(pfOutput++) = 
   pfBuffer[((lSampleIndex + 
   lBufferReadOffset) & lBufferSizeMinusOne)];
 /* gain: 0.871402, 0.882762, 0.891443, 0.901117  */
    pfBuffer[((lSampleIndex + lBufferWriteOffset) & 
      lBufferSizeMinusOne)] = 
      (fInputSample +
      fFeedback * pfBuffer[((lSampleIndex + 
      lBufferReadOffset) & lBufferSizeMinusOne)]);
  }
  psSimpleDelayLine -> m_lWritePointer
    = ((psSimpleDelayLine -> m_lWritePointer 
     + SampleCount) & lBufferSizeMinusOne);
}
/***********************************************************/
void cleanupSimpleDelayLine(LADSPA_Handle Instance){
  SimpleDelayLine * psSimpleDelayLine;
  psSimpleDelayLine = (SimpleDelayLine *)Instance;
  free(psSimpleDelayLine -> m_pfBuffer);
  free(psSimpleDelayLine);
}
/***********************************************************/
LADSPA_Descriptor *g_psDescriptor = NULL;
/***********************************************************/
void _init() {
  char ** pcPortNames;
  LADSPA_PortDescriptor * piPortDescriptors;
  LADSPA_PortRangeHint * psPortRangeHints;
  g_psDescriptor  = 
    (LADSPA_Descriptor *)malloc(sizeof(LADSPA_Descriptor));
  if (g_psDescriptor) {
    g_psDescriptor->UniqueID = 9;
    g_psDescriptor->Label = strdup("comb");
    g_psDescriptor->Properties = 
       LADSPA_PROPERTY_HARD_RT_CAPABLE;
    g_psDescriptor->Name = strdup("Namagi: comb ver.130125");
    g_psDescriptor->Maker = strdup("Namagi Products");
    g_psDescriptor->Copyright = strdup("None");
    g_psDescriptor->PortCount = 4;
    piPortDescriptors = 
      (LADSPA_PortDescriptor *)calloc(4, 
      sizeof(LADSPA_PortDescriptor));
    g_psDescriptor -> PortDescriptors = 
      (const LADSPA_PortDescriptor *)piPortDescriptors;
    piPortDescriptors[SDL_DELAY] = 
      LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
    piPortDescriptors[SDL_FEEDBACK] = 
       LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
    piPortDescriptors[SDL_INPUT] = 
       LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO;
    piPortDescriptors[SDL_OUTPUT] = 
       LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO;
    pcPortNames = (char **)calloc(4, sizeof(char *));
    g_psDescriptor->PortNames = 
       (const char **)pcPortNames;
    pcPortNames[SDL_DELAY] = strdup("DelayTime(msec)");
    pcPortNames[SDL_FEEDBACK] = strdup("Feedback");
    pcPortNames[SDL_INPUT] = strdup("Input");
    pcPortNames[SDL_OUTPUT] = strdup("Output");
    psPortRangeHints  = 
      ((LADSPA_PortRangeHint *)calloc(4,
       sizeof(LADSPA_PortRangeHint)));
    g_psDescriptor -> PortRangeHints = 
       (const LADSPA_PortRangeHint *)psPortRangeHints;

    psPortRangeHints[SDL_DELAY].HintDescriptor
      = (LADSPA_HINT_BOUNDED_BELOW | 
        LADSPA_HINT_BOUNDED_ABOVE | 
        LADSPA_HINT_DEFAULT_MAXIMUM);
    psPortRangeHints[SDL_DELAY].LowerBound  = 0;
    psPortRangeHints[SDL_DELAY].UpperBound  = 
       (LADSPA_Data)MAX_DELAY; 
    psPortRangeHints[SDL_FEEDBACK].HintDescriptor = 
       (LADSPA_HINT_BOUNDED_BELOW 
    | LADSPA_HINT_BOUNDED_ABOVE 
           | LADSPA_HINT_DEFAULT_HIGH);
    psPortRangeHints[SDL_FEEDBACK].LowerBound = -1;
    psPortRangeHints[SDL_FEEDBACK].UpperBound = 1;
    psPortRangeHints[SDL_INPUT].HintDescriptor = 0;
    psPortRangeHints[SDL_OUTPUT].HintDescriptor = 0;
    g_psDescriptor -> instantiate = 
      instantiateSimpleDelayLine;
    g_psDescriptor -> connect_port = 
      connectPortToSimpleDelayLine;
    g_psDescriptor -> activate = activateSimpleDelayLine;
    g_psDescriptor -> run = runSimpleDelayLine;
    g_psDescriptor -> run_adding = NULL;
    g_psDescriptor -> set_run_adding_gain = NULL;
    g_psDescriptor -> deactivate = NULL;
    g_psDescriptor -> cleanup = cleanupSimpleDelayLine;
  }
}
/***********************************************************/
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;
}
/***********************************************************/
/* EOF */


Windows用に作る場合は、コマンドプロンプトからコンパイルするときは、下のようにやった。
gcc -shared -o namagi_comb.dll namagi_comb.c -ID
これでnamagi_comb.dllが同じ階層に作られるので、それをAudacityのPlug-Insフォルダに入れると使えるようになる。



sound programming 目次