LADSPA Delay
LADSPAのプラグインを実験的に作ってみる。今回はシンプルなディレイで、msecでディレイタイムを設定し、dBでDry,Wetのレベル調整を行い、フィードバックは-1から1というオーソドックスなもの。LADSPAにあったサンプルのディレイはフィードバックもなくsec単位、パラメータはリニア単位だったので、その辺を改造してみた。DryのON/OFFも追加。実験用に使うので、パラメータは少なくして基本的にモノラル用とした。LADSPAは初期設定値が細かくコントロールできないので、パラメーターが多いと面倒になる。3つぐらいの入力であればストレスはないので実用になる。
VSTもそうだが、この手のプラグインは、パラメーター含めてfloatで管理していたりして、個人的には窮屈に感じる。やっぱりフルオリジナルの方が面白いわ。ただ0から作る必要がないので、ちょろっと試すには手軽かもしれない。LADSPAはシンプルな仕様なので、ladspa.hを眺めていれば、何となく出来てしまうよさはある。
ブロック図は極めてシンプル。
ソースはビット演算を使ったディレイ。バッファは構造体に設置しないとうまく動作しなかった。LADSPAの仕様がよく分かっていないのだが、いろいろ制約があるようだ。
Windowsでのコンパイルは以下のコマンドを使った。
ビット演算の説明
<<= は左シフト代入演算子といって、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 目次はこちら
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 目次はこちら