LADSPA Delay Multi-Tap
リバーブの初期反射の予備実験としてマルチタップディレイをプログラミングしてみた。マルチタップディレイは、ディレイラインに対して、任意の位置に複数のタップを作り、信号を取り出し、合成するという仕組み。ブロック図にすると以下のような構造になっている。
リバーブにおける初期反射は、その空間の印象を左右する重要な部分。複雑な反射をシミュレートする必要があるため、通常のFB付のディレイでは初期反射をうまく表現できない。そこでマルチタップの出番となる。
サクサクっと作ってみた。以前作ったディレイの改造なので30分程度で完成。今回は実験なので6タップ、モノラル、FBなしとした。これ以上のタップ数だとパラメーターから1個1個入力するのは現実的ではない。設定ファイルを別に設けた方がよさそうだ。
下がデルタ関数への適用例。設定次第でいろいろな特性を作り出せる。このマルチタップは、プログラムを作ることは簡単だが、使える設定を見つけ出すことは容易ではない。実際の初期反射のデータを分析したり、試行錯誤が必要。また実用とするなら、タップ数は数十は欲しいと思う。しかも左右のチャンネルで欲しいので、その2倍となる。設定項目は100以上となり、狙い通りの音を見つけるのに苦労しそうだ。むやみにやるよりも、理論的に導き出す方がよいと思われる。何か方法論を思いついたら、実用マルチタップにチャレンジしてみようかと思う。
実際に適用した例が以下のサンプル。 まずは何もしていないドライな音。
次にマルチタップを適用した音。6タップでFBもないが、初期反射ぽさは感じられる。LPFや後部残響を組み合わせれば十分使えそうだ。
sound programming 目次はこちら
リバーブにおける初期反射は、その空間の印象を左右する重要な部分。複雑な反射をシミュレートする必要があるため、通常のFB付のディレイでは初期反射をうまく表現できない。そこでマルチタップの出番となる。
サクサクっと作ってみた。以前作ったディレイの改造なので30分程度で完成。今回は実験なので6タップ、モノラル、FBなしとした。これ以上のタップ数だとパラメーターから1個1個入力するのは現実的ではない。設定ファイルを別に設けた方がよさそうだ。
下がデルタ関数への適用例。設定次第でいろいろな特性を作り出せる。このマルチタップは、プログラムを作ることは簡単だが、使える設定を見つけ出すことは容易ではない。実際の初期反射のデータを分析したり、試行錯誤が必要。また実用とするなら、タップ数は数十は欲しいと思う。しかも左右のチャンネルで欲しいので、その2倍となる。設定項目は100以上となり、狙い通りの音を見つけるのに苦労しそうだ。むやみにやるよりも、理論的に導き出す方がよいと思われる。何か方法論を思いついたら、実用マルチタップにチャレンジしてみようかと思う。
実際に適用した例が以下のサンプル。 まずは何もしていないドライな音。
次にマルチタップを適用した音。6タップでFBもないが、初期反射ぽさは感じられる。LPFや後部残響を組み合わせれば十分使えそうだ。
LADSPA Delay Multi-Tap
/* namagi_delay_multitap.c 2017.08.15 windows compile gcc -shared -o namagi_delay_multitap.dll namagi_delay_multitap.c -ID compile Ubuntu gcc -fPIC -DPIC -shared -nostartfiles -o namagi_delay_multitap.so namagi_delay_multitap.c */ /***********************************************************/ #include <stdlib.h> #include <string.h> #include <math.h> #include "ladspa.h" /***********************************************************/ #define INPUT 0 #define OUTPUT 1 #define DELAY_TIME1 2 #define WET1 3 #define DELAY_TIME2 4 #define WET2 5 #define DELAY_TIME3 6 #define WET3 7 #define DELAY_TIME4 8 #define WET4 9 #define DELAY_TIME5 10 #define WET5 11 #define DELAY_TIME6 12 #define WET6 13 #define DRYCHECK 14 #define DRY 15 /***********************************************************/ /* 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_pfDelay1; float *m_pfWet1; float *m_pfDelay2; float *m_pfWet2; float *m_pfDelay3; float *m_pfWet3; float *m_pfDelay4; float *m_pfWet4; float *m_pfDelay5; float *m_pfWet5; float *m_pfDelay6; float *m_pfWet6; 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; psDelay -> m_lBufferSize = 1; while (psDelay -> m_lBufferSize < SampleRate){ psDelay -> m_lBufferSize <<= 1; } 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; 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_TIME1: psDelay -> m_pfDelay1 = DataLocation; break; case WET1: psDelay -> m_pfWet1 = DataLocation; break; case DELAY_TIME2: psDelay -> m_pfDelay2 = DataLocation; break; case WET2: psDelay -> m_pfWet2 = DataLocation; break; case DELAY_TIME3: psDelay -> m_pfDelay3 = DataLocation; break; case WET3: psDelay -> m_pfWet3 = DataLocation; break; case DELAY_TIME4: psDelay -> m_pfDelay4 = DataLocation; break; case WET4: psDelay -> m_pfWet4 = DataLocation; break; case DELAY_TIME5: psDelay -> m_pfDelay5 = DataLocation; break; case WET5: psDelay -> m_pfWet5 = DataLocation; break; case DELAY_TIME6: psDelay -> m_pfDelay6 = DataLocation; break; case WET6: psDelay -> m_pfWet6 = 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 fWet1 = *(psDelay -> m_pfWet1); float fWet2 = *(psDelay -> m_pfWet2); float fWet3 = *(psDelay -> m_pfWet3); float fWet4 = *(psDelay -> m_pfWet4); float fWet5 = *(psDelay -> m_pfWet5); float fWet6 = *(psDelay -> m_pfWet6); float fDryCheckBox = *(psDelay -> m_pfDryCheckBox); float fDry = *(psDelay -> m_pfDry); unsigned long lBufferSizeMinusOne = psDelay -> m_lBufferSize - 1; unsigned long lDelay1 = *(psDelay -> m_pfDelay1) * psDelay -> m_lSampleRate / 1000; unsigned long lDelay2 = *(psDelay -> m_pfDelay2) * psDelay -> m_lSampleRate / 1000; unsigned long lDelay3 = *(psDelay -> m_pfDelay3) * psDelay -> m_lSampleRate / 1000; unsigned long lDelay4 = *(psDelay -> m_pfDelay4) * psDelay -> m_lSampleRate / 1000; unsigned long lDelay5 = *(psDelay -> m_pfDelay5) * psDelay -> m_lSampleRate / 1000; unsigned long lDelay6 = *(psDelay -> m_pfDelay6) * psDelay -> m_lSampleRate / 1000; unsigned long lBufferWriteOffset = psDelay -> m_lWritePointer; unsigned long lBufferReadOffset1 = lBufferWriteOffset + psDelay -> m_lBufferSize - lDelay1; unsigned long lBufferReadOffset2 = lBufferWriteOffset + psDelay -> m_lBufferSize - lDelay2; unsigned long lBufferReadOffset3 = lBufferWriteOffset + psDelay -> m_lBufferSize - lDelay3; unsigned long lBufferReadOffset4 = lBufferWriteOffset + psDelay -> m_lBufferSize - lDelay4; unsigned long lBufferReadOffset5 = lBufferWriteOffset + psDelay -> m_lBufferSize - lDelay5; unsigned long lBufferReadOffset6 = lBufferWriteOffset + psDelay -> m_lBufferSize - lDelay6; unsigned long i; for (i=0; i < SampleCount; i++){ fInputSample = *(pfInput++); if(fDryCheckBox == 1){ *(pfOutput++) = pow(10,fDry/20.0) * fInputSample + pow(10,fWet1/20.0) * pfBuffer[(i + lBufferReadOffset1) & lBufferSizeMinusOne] + pow(10,fWet2/20.0) * pfBuffer[(i + lBufferReadOffset2) & lBufferSizeMinusOne] + pow(10,fWet3/20.0) * pfBuffer[(i + lBufferReadOffset3) & lBufferSizeMinusOne] + pow(10,fWet4/20.0) * pfBuffer[(i + lBufferReadOffset4) & lBufferSizeMinusOne] + pow(10,fWet5/20.0) * pfBuffer[(i + lBufferReadOffset5) & lBufferSizeMinusOne] + pow(10,fWet6/20.0) * pfBuffer[(i + lBufferReadOffset6) & lBufferSizeMinusOne]; }else{ *(pfOutput++) = pow(10,fWet1/20.0) * pfBuffer[(i + lBufferReadOffset1) & lBufferSizeMinusOne] + pow(10,fWet2/20.0) * pfBuffer[(i + lBufferReadOffset2) & lBufferSizeMinusOne] + pow(10,fWet3/20.0) * pfBuffer[(i + lBufferReadOffset3) & lBufferSizeMinusOne] + pow(10,fWet4/20.0) * pfBuffer[(i + lBufferReadOffset4) & lBufferSizeMinusOne] + pow(10,fWet5/20.0) * pfBuffer[(i + lBufferReadOffset5) & lBufferSizeMinusOne] + pow(10,fWet6/20.0) * pfBuffer[(i + lBufferReadOffset6) & lBufferSizeMinusOne]; } pfBuffer[(i + lBufferWriteOffset) & lBufferSizeMinusOne] = fInputSample; } psDelay -> m_lWritePointer = ((psDelay -> m_lWritePointer + SampleCount) &lBufferSizeMinusOne); } /***********************************************************/ void cleanupDelay(LADSPA_Handle Instance){ Delay * psDelay; psDelay = (Delay *)Instance; free(psDelay); } /***********************************************************/ LADSPA_Descriptor *g_psDescriptor = NULL; /***********************************************************/ void _init() { char ** pcPortNames; LADSPA_PortDescriptor * piPortDescriptors; LADSPA_PortRangeHint * psPortRangeHints; int ports = 16; g_psDescriptor = (LADSPA_Descriptor *)malloc(sizeof(LADSPA_Descriptor)); if (g_psDescriptor) { g_psDescriptor->UniqueID = 0; g_psDescriptor->Label = strdup("Delay Multi-Tap"); g_psDescriptor->Properties = LADSPA_PROPERTY_HARD_RT_CAPABLE; g_psDescriptor->Name = strdup("Namagi: Delay Multi-Tap ver.170815"); 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_TIME1] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; piPortDescriptors[WET1] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; piPortDescriptors[DELAY_TIME2] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; piPortDescriptors[WET2] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; piPortDescriptors[DELAY_TIME3] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; piPortDescriptors[WET3] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; piPortDescriptors[DELAY_TIME4] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; piPortDescriptors[WET4] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; piPortDescriptors[DELAY_TIME5] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; piPortDescriptors[WET5] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; piPortDescriptors[DELAY_TIME6] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; piPortDescriptors[WET6] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; piPortDescriptors[DRYCHECK] = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; piPortDescriptors[DRY] = 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_TIME1] = strdup("delay1 (msec)"); pcPortNames[WET1] = strdup("wet1 (dB)"); pcPortNames[DELAY_TIME2] = strdup("delay2 (msec)"); pcPortNames[WET2] = strdup("wet2 (dB)"); pcPortNames[DELAY_TIME3] = strdup("delay3 (msec)"); pcPortNames[WET3] = strdup("wet3 (dB)"); pcPortNames[DELAY_TIME4] = strdup("delay4 (msec)"); pcPortNames[WET4] = strdup("wet4 (dB)"); pcPortNames[DELAY_TIME5] = strdup("delay5 (msec)"); pcPortNames[WET5] = strdup("wet5 (dB)"); pcPortNames[DELAY_TIME6] = strdup("delay6 (msec)"); pcPortNames[WET6] = strdup("wet6 (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; /* 1 */ psPortRangeHints[DELAY_TIME1].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_LOW); psPortRangeHints[DELAY_TIME1].LowerBound = 0; psPortRangeHints[DELAY_TIME1].UpperBound = 1000.0; psPortRangeHints[WET1].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_0); psPortRangeHints[WET1].LowerBound = -120; psPortRangeHints[WET1].UpperBound = 0; /* 2 */ psPortRangeHints[DELAY_TIME2].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_LOW); psPortRangeHints[DELAY_TIME2].LowerBound = 0; psPortRangeHints[DELAY_TIME2].UpperBound = 1000.0; psPortRangeHints[WET2].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_0); psPortRangeHints[WET2].LowerBound = -120; psPortRangeHints[WET2].UpperBound = 0; /* 3 */ psPortRangeHints[DELAY_TIME3].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_LOW); psPortRangeHints[DELAY_TIME3].LowerBound = 0; psPortRangeHints[DELAY_TIME3].UpperBound = 1000.0; psPortRangeHints[WET3].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_0); psPortRangeHints[WET3].LowerBound = -120; psPortRangeHints[WET3].UpperBound = 0; /* 4 */ psPortRangeHints[DELAY_TIME4].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_LOW); psPortRangeHints[DELAY_TIME4].LowerBound = 0; psPortRangeHints[DELAY_TIME4].UpperBound = 1000.0; psPortRangeHints[WET4].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_0); psPortRangeHints[WET4].LowerBound = -120; psPortRangeHints[WET4].UpperBound = 0; /* 5 */ psPortRangeHints[DELAY_TIME5].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_LOW); psPortRangeHints[DELAY_TIME5].LowerBound = 0; psPortRangeHints[DELAY_TIME5].UpperBound = 1000.0; psPortRangeHints[WET5].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_0); psPortRangeHints[WET5].LowerBound = -120; psPortRangeHints[WET5].UpperBound = 0; /* 6 */ psPortRangeHints[DELAY_TIME6].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_LOW); psPortRangeHints[DELAY_TIME6].LowerBound = 0; psPortRangeHints[DELAY_TIME6].UpperBound = 1000.0; psPortRangeHints[WET6].HintDescriptor = (LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_DEFAULT_0); psPortRangeHints[WET6].LowerBound = -120; psPortRangeHints[WET6].UpperBound = 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[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; } |
sound programming 目次はこちら