2012/12/24

C言語 LADSPAプラグイン アンプ改造

LADSPA(ラドスパ)とは

C言語の学習をさぼって半年経ってしまった。リハビリということでボチボチ再開してみようかと思う。実用的なプログラムを作った方がモチベーションも下がりにくいのでLADSPAにチャレンジしてみる。

LADSPAとはLinux Audio Developers Simple Plugin APIの略で、2000年に誕生したLinux発祥のデジタル音声信号の加工、生成規格。スタインバーグのVSTと似たようなもの。移植性の高さからWinでもMacでも利用できる。個人的によく使っているAudacityにもLADSPAのプラグインがいくつか入っている。

開発する立場から見るとLADSPAは簡素で扱いやすく思えた。以前VSTでも作ってみようかと思って開発環境だけ整えてみたけど、あまりにも巨大環境過ぎて、すぐにやめてしまった。マイクロソフトのVisual C++は、仕事で使う人にとってはよい開発環境なのかもしれないけど、趣味でちょろっと作るには本格的過ぎて・・・ その点、LADSPAは純粋なC言語で手軽に作ることができる。テキストエディタでちょろっと書いてGCCで手軽にコンパイル。開発に必要なのはladspa.hというヘッダファイルだけあればいい。PCのディスクスペースも食わず、なんともお手軽だ。

まずはサンプルを改造

さっそく作ってみようと思って調べてみる。まずはオフィシャルページにアクセスしてSDK(ladspa_sdk_1.13.tgz)を入手。

LADSPAオフィシャル
http://www.ladspa.org/

SDKをダウンロードしてサンプルを眺めるところからスタート。まずはサンプルをコンパイルしてAudacityで実行してみる。LADSPAはLinux用なので、Windowsで実行する場合はdllを作る必要がある。そこでソースファイルにちょっと手を加えてdllを作ってみた。 参考になる解説サイトが皆無に等しく、海外のいくつかのサイトやら、掲示板を見て、その方法を試行錯誤。LADSPAの開発の困難さは情報不足にありそうだな。日本人で開発している人はほとんどいなさそうだわ。日本語情報はあきらめよう。 海外でもそれほど活発に開発されているとは言えない。それでもWindowsのAudacityで使えるようになったので内部構造を理解してしまえば、いろいろ作れそうだ。ただルールをある程度は理解しておかないとダメだと思われる。今まで言語仕様だけでプログラムを作ってきたから、何かのルールに沿って作るのは面倒くさいなぁ。まずはサンプルの改造あたりから始めて、徐々にオリジナルを作って行こうと思う。最終的にはAudacityで使えるエフェクターを作ろうと思っているが、何をつくろうかな? まだ作ったことがないリバーブかな?

下はサンプルのampを改造してWindows版Audacityで実行した状態。ゲインの幅やら、入出力関係をいろいろいじって実験中。音声信号は32bit floatなのね。予想通り、かなりお手軽だわ。


アンプ プラグイン サンプル改造

サンプルのアンプを改造して、よりシンプルなソースコードにするためにモノラル専用アンプにしてみた。ただAudacityで扱う場合は、ステレオトラックに適用しても問題なく機能する。 ソースコードは結構ダラダラと長くなる。ただシンプルなので、慣れればそれほど厄介ではないはず。
/* namagi_amp.c 2012.12.24
compile windows
gcc -shared -o namagi_amp.dll namagi_amp.c -ID 

compile Ubuntu
gcc -fPIC -DPIC -shared -nostartfiles -o namagi_amp.so namagi_amp.c
*/
/**********************************************************/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
/**********************************************************/
/*LADSPAのヘッダファイルを読み込む*/
#include "ladspa.h"
/**********************************************************/
/* マイクロ定数を定義 コントロールと出力 */
#define AMP_CONTROL 0
#define AMP_INPUT1  1
#define AMP_OUTPUT1 2
/**********************************************************/
/* Windowsでdllを生成するため条件コンパイルを行う。
   WIN32というマイク定義がされていたならば実行しdllを作る。
   プラットフォームごとの処理をわけるための#ifdefを使う。
   winでコンパイルするとWIN32が定義されるので有効になる。
   unixでは無効と判断される。
*/
#ifdef WIN32
   int bIsFirstTime = 1; /* 定義して1と設定 */
   void _init(); /* ここから読まれる */
   /* プリプロセッサ dllを作成するためのマクロ */
   #define _WINDOWS_DLL_EXPORT_ __declspec(dllexport)
#else
   #define _WINDOWS_DLL_EXPORT_ 
#endif
/**********************************************************/
/* 構造体で入出力とコントロールを定義 */
typedef struct {
  LADSPA_Data * m_pfControlValue; /* コントロール */
  LADSPA_Data * m_pfInputBuffer1; /* mono 入力 */
  LADSPA_Data * m_pfOutputBuffer1; /* mono 出力 */
} Amplifier;
/**********************************************************/
/* インスタンス化 LADSPA_Handleはポインタ型のtypedef宣言 */
LADSPA_Handle instantiateAmplifier(const LADSPA_Descriptor * Descriptor,
       unsigned long SampleRate) {
  return malloc(sizeof(Amplifier));
}
/**********************************************************/
/* portに接続 コントロールと入出力 */
void connectPortToAmplifier(LADSPA_Handle Instance,
         unsigned long Port,
         LADSPA_Data * DataLocation) 
{
  Amplifier * psAmplifier;
  psAmplifier = (Amplifier *)Instance;

  switch (Port) {
  case AMP_CONTROL:
    psAmplifier->m_pfControlValue = DataLocation;
    break;
  case AMP_INPUT1:
    psAmplifier->m_pfInputBuffer1 = DataLocation;
    break;
  case AMP_OUTPUT1:
    psAmplifier->m_pfOutputBuffer1 = DataLocation;
    break;
  }
}
/**********************************************************/
/* モノラルアンプ処理 */
void runMonoAmplifier(LADSPA_Handle Instance,
   unsigned long SampleCount) 
{  
  LADSPA_Data *pfInput;
  LADSPA_Data *pfOutput;
  LADSPA_Data fGain;
  Amplifier *psAmplifier;
  unsigned long lSampleIndex;

  psAmplifier = (Amplifier *)Instance;

  pfInput = psAmplifier->m_pfInputBuffer1;
  pfOutput = psAmplifier->m_pfOutputBuffer1;
  fGain = *(psAmplifier->m_pfControlValue);

  /* 実際の処理はここだけ */
  for (lSampleIndex = 0; lSampleIndex < SampleCount; lSampleIndex++) 
    *(pfOutput++) = *(pfInput++) * fGain;
}
/**********************************************************/
/* 開放 */
void cleanupAmplifier(LADSPA_Handle Instance) {
  free(Instance);
}
/**********************************************************/
/*  */
LADSPA_Descriptor * g_psMonoDescriptor = NULL;
/**********************************************************/
/* 最初に呼び出される部分 インターフェイスやエフェクト情報の記述 */
void _init() 
{
  char ** pcPortNames;
  LADSPA_PortDescriptor * piPortDescriptors;
  LADSPA_PortRangeHint * psPortRangeHints;

    g_psMonoDescriptor
      = (LADSPA_Descriptor *)malloc(sizeof(LADSPA_Descriptor));
    g_psMonoDescriptor->UniqueID
      = 1048;
    g_psMonoDescriptor->Label
      = strdup("amplifier");
    g_psMonoDescriptor->Properties
      = LADSPA_PROPERTY_HARD_RT_CAPABLE;
    g_psMonoDescriptor->Name 
      = strdup("Namagi: Amplifier Linear ver.121224");
    g_psMonoDescriptor->Maker
      = strdup("Namagi Products");
    g_psMonoDescriptor->Copyright
      = strdup("None");
    g_psMonoDescriptor->PortCount
      = 3;
    piPortDescriptors
      = (LADSPA_PortDescriptor *)calloc(3, 
        sizeof(LADSPA_PortDescriptor));
    g_psMonoDescriptor->PortDescriptors
      = (const LADSPA_PortDescriptor *)piPortDescriptors;
    piPortDescriptors[AMP_CONTROL]
      = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
    piPortDescriptors[AMP_INPUT1]
      = LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO;
    piPortDescriptors[AMP_OUTPUT1]
      = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO;
    pcPortNames
      = (char **)calloc(3, sizeof(char *));
    g_psMonoDescriptor->PortNames 
      = (const char **)pcPortNames;
    pcPortNames[AMP_CONTROL]
      = strdup("Gain");
    pcPortNames[AMP_INPUT1]
      = strdup("Input");
    pcPortNames[AMP_OUTPUT1]
      = strdup("Output");
    psPortRangeHints
      = ((LADSPA_PortRangeHint *)
        calloc(3, sizeof(LADSPA_PortRangeHint)));
    g_psMonoDescriptor->PortRangeHints
      = (const LADSPA_PortRangeHint *)psPortRangeHints;
    psPortRangeHints[AMP_CONTROL].HintDescriptor
      = (LADSPA_HINT_BOUNDED_BELOW 
        | LADSPA_HINT_LOGARITHMIC
        | LADSPA_HINT_DEFAULT_1);
    psPortRangeHints[AMP_CONTROL].LowerBound 
      = 0;
    psPortRangeHints[AMP_INPUT1].HintDescriptor
      = 0;
    psPortRangeHints[AMP_OUTPUT1].HintDescriptor
      = 0;
    g_psMonoDescriptor->instantiate 
      = instantiateAmplifier;
    g_psMonoDescriptor->connect_port 
      = connectPortToAmplifier;
    g_psMonoDescriptor->activate
      = NULL;
    g_psMonoDescriptor->run
      = runMonoAmplifier;
    g_psMonoDescriptor->run_adding
      = NULL;
    g_psMonoDescriptor->set_run_adding_gain
      = NULL;
    g_psMonoDescriptor->deactivate
      = NULL;
    g_psMonoDescriptor->cleanup
      = cleanupAmplifier;
}
/**********************************************************/
/* 開放 インターフェイスまわり */
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) { /* 変数は1になっている if文は0の場合実行しない */
  _init();            /* 実行する */
  bIsFirstTime = 0;   /* 0にしてしまう */
  }
#endif
  if (Index == 0)
    return g_psMonoDescriptor;
  else
    return NULL;
}


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

Windowsでは、namagi_amp.dllが同じ階層に作られるので、それをAudacityのPlug-Insフォルダに入れると使えるようになる。


Ubuntu環境では、以下のコマンドを使って.soファイルを作成。
gcc -fPIC -DPIC -shared -nostartfiles -o namagi_amp.so namagi_amp.c

できたnamagi_amp.soを隠しフォルダの
/home/usr/.audacity-data/Plug-Ins/
に入れればAudacityで使用できる。


主に使われている関数の中で、記述するのは以下の★部分だけ。 基本的に必要なポートを追加し、処理を記述すれば、それだけで動く。

typedef struct{}StructEffect;★新しいポート追加
LADSPA_Handle instantiateEffect(){}
void connectPortToEffect(){}★新しいポート追加
void runStereoEffect(){}★処理の記述
void cleanupEffect(){}
LADSPA_Descriptor *g_psDescriptor = NULL;
void _init(){}★新しいポート追加 インターフェイス
void deleteDescriptor(){}
void _fini(){}


sound programming 目次
C言語 ANSI C89 Meadow & MinGW GCC 目次