リミッター

『雲仙』フレームワークの「ドッグフードの味見」として作ったシンセサイザーに、リミッターを取り付けました。リミッターはほぼ全てのオーディオ信号処理装置で必要になるモジュールです。

リミッター

整数のオーバーフローは、注意深さにかけるプログラマが引き起こす現象です。

整数のオーバーフローが発生すると、一般的なコンピュータでは数値が正の最大から負の絶対値最大までいっきに飛びます。そのため、オーディオ信号処理ではステップ状の信号となり、非常に不快な音となります。このような正から負への値のジャンプをwrap aroundと呼びます。

DSPにはwrap aroundへの対応策として、通常saturation機能が実装されます。saturationはオーバーフローが発生したときに強制的に値を正の最大値か負の絶対値最大の近いほうに固定する機能です。これでwrap aroundの不快な音は防ぐことができますが、ざりざりした不快な音になることは変わりません。

浮動小数点演算をつかうと、少なくとも実用レベルのオーディオ信号処理では演算途中のオーバーフローから開放されます。これは大変助かる話で、CORTEX-M7Fプラットホーム用に雲仙を開発した動機そのものです。

ところが、浮動小数点演算に対応したプロセッサであっても、DACは整数しか受け付けませんので変換時にオーバーフローが起きます。

リミッターはオーバーフローを防ぐ方法です。広義のリミッターにはsaturationも含まれており、その場合はハードリミッターなどと呼ばれることがあります。それに対して、一気に最大値にするのではなく、緩やかなカーブを以って信号の頭を抑える方法をソフト・リミッターなどとも呼びます。

リミッターはもともとスタジオなどで使われるアナログ装置から名前が来ています。正式に定まった名称はなく、多くの場合、コンプレッサーとリミッターが混同されて使われています。

実装

今回使用したリミッターはamakusaライブラリにあらかじめ用意していたものです。このリミッターは入力の範囲が[-0.5, 0.5]の場合にはそのままリニアに値を出力します。そしてその外側にある値はarctan()関数を使って[-1.0, 1.0]の範囲に収まるよう調整しています。なお、リニアな範囲とarctan関数への切り替えは、導関数が連続になるように調整しています。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
namespace amakusa
{
/**
* @brief A limitter. linear at [-0.5,0.5]
*/
class LimitterLinAtan : public AbstractFilter
{
public:
LimitterLinAtan( uint32_t blockSize ): AbstractFilter(blockSize ){};
virtual void run( float32_t *pSrc, float32_t *pDst);
};
}
namespace amakusa { /** * @brief A limitter. linear at [-0.5,0.5] */ class LimitterLinAtan : public AbstractFilter { public: LimitterLinAtan( uint32_t blockSize ): AbstractFilter(blockSize ){}; virtual void run( float32_t *pSrc, float32_t *pDst); }; }
namespace amakusa
{
    /**
    * @brief A limitter. linear at [-0.5,0.5]
    */
    class LimitterLinAtan : public AbstractFilter
    {
    public:
        LimitterLinAtan( uint32_t blockSize ): AbstractFilter(blockSize ){};
        virtual void run( float32_t *pSrc, float32_t *pDst);
    };
}

 

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
void amakusa::LimitterLinAtan::run( float32_t *pSrc, float32_t *pDst)
{
uint32_t count = this->block_size;
for ( int i=0; i<count; i++ )
{
if ( pSrc[i] > 0.5F )
pDst[i] = std::atan((pSrc[i]-0.5F)*M_PI)/M_PI+0.5F;
else if ( -0.5F > pSrc[i] )
pDst[i] = std::atan((pSrc[i]+0.5F)*M_PI)/M_PI-0.5F;
else
pDst[i] = pSrc[i];
}
}
void amakusa::LimitterLinAtan::run( float32_t *pSrc, float32_t *pDst) { uint32_t count = this->block_size; for ( int i=0; i<count; i++ ) { if ( pSrc[i] > 0.5F ) pDst[i] = std::atan((pSrc[i]-0.5F)*M_PI)/M_PI+0.5F; else if ( -0.5F > pSrc[i] ) pDst[i] = std::atan((pSrc[i]+0.5F)*M_PI)/M_PI-0.5F; else pDst[i] = pSrc[i]; } }
void amakusa::LimitterLinAtan::run( float32_t *pSrc, float32_t *pDst)
{
    uint32_t count = this->block_size;
    
    for ( int i=0; i<count; i++ )
    {
        if ( pSrc[i] > 0.5F )
            pDst[i] = std::atan((pSrc[i]-0.5F)*M_PI)/M_PI+0.5F;
        else if ( -0.5F > pSrc[i] )
            pDst[i] = std::atan((pSrc[i]+0.5F)*M_PI)/M_PI-0.5F;
        else
            pDst[i] = pSrc[i];

    }
}

 

利用方法

amakusa::LimitterLinAtanクラスはamakusa::AbstractFilterから派生させているため、同様のフィルタと同じくオブジェクトを作ってrun()メソッドを呼ぶだけで使えます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// constructor.
Monophonic::Monophonic( unsigned int a_block_size )
{
this->block_size = a_block_size;
// initializing the subm-odules.
this->vfo = new VFO(block_size); // allocate VFO
this->dc_blocker = new DCBlocker( this->block_size );
this->sv_filter = new SVFilter( this->block_size );
this->eg = new EG( this->block_size );
this->limitter = new amakusa::LimitterLinAtan( this->block_size );
work_buf_a = new float32_t[block_size];
work_buf_b = new float32_t[block_size];
} // End of constructor()
// constructor. Monophonic::Monophonic( unsigned int a_block_size ) { this->block_size = a_block_size; // initializing the subm-odules. this->vfo = new VFO(block_size); // allocate VFO this->dc_blocker = new DCBlocker( this->block_size ); this->sv_filter = new SVFilter( this->block_size ); this->eg = new EG( this->block_size ); this->limitter = new amakusa::LimitterLinAtan( this->block_size ); work_buf_a = new float32_t[block_size]; work_buf_b = new float32_t[block_size]; } // End of constructor()
    // constructor. 
Monophonic::Monophonic( unsigned int a_block_size )
{
    this->block_size = a_block_size;
    
        // initializing the subm-odules.
    this->vfo = new VFO(block_size);      // allocate VFO
    this->dc_blocker = new DCBlocker( this->block_size );
    this->sv_filter = new SVFilter( this->block_size );
    this->eg = new EG( this->block_size );
    this->limitter = new amakusa::LimitterLinAtan( this->block_size );
    
    work_buf_a = new float32_t[block_size];
    work_buf_b = new float32_t[block_size];
    
}   // End of constructor()

 

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Run all signal processing.
void Monophonic::run(
float out_buffer[] // place to write the right output samples
)
{
// place the signal processing coce here
this->vfo->run( work_buf_a );
// blocking DC.
this->dc_blocker->run( work_buf_a, work_buf_b );
// applying filter.
this->sv_filter->run( work_buf_b, work_buf_a );
// generate envelope
this->eg->run( work_buf_b );
// apply envelope
for ( int i= 0; i< this->block_size; i++ )
work_buf_b[ i ] *= work_buf_a[ i ];
// apply amplitude limitter
this->limitter->run( work_buf_b, out_buffer );
} // End of run()
// Run all signal processing. void Monophonic::run( float out_buffer[] // place to write the right output samples ) { // place the signal processing coce here this->vfo->run( work_buf_a ); // blocking DC. this->dc_blocker->run( work_buf_a, work_buf_b ); // applying filter. this->sv_filter->run( work_buf_b, work_buf_a ); // generate envelope this->eg->run( work_buf_b ); // apply envelope for ( int i= 0; i< this->block_size; i++ ) work_buf_b[ i ] *= work_buf_a[ i ]; // apply amplitude limitter this->limitter->run( work_buf_b, out_buffer ); } // End of run()
        // Run all signal processing.
void Monophonic::run(           
        float out_buffer[]    // place to write the right output samples
        )
{
        // place the signal processing coce here
    this->vfo->run( work_buf_a );
    
        // blocking DC.
    this->dc_blocker->run( work_buf_a, work_buf_b );

        // applying filter.
    this->sv_filter->run( work_buf_b, work_buf_a );
    
        // generate envelope
    this->eg->run( work_buf_b );
    
        // apply envelope
    for ( int i= 0; i< this->block_size; i++ )
        work_buf_b[ i ] *= work_buf_a[ i ];
        
        // apply amplitude limitter
    this->limitter->run( work_buf_b, out_buffer );
        
}   // End of run()

 

まとめ

『雲仙』オーディオフレームワーク用に開発したシンセサイザーは、これで一応の完成を見ました。ネットを見回すと面白そうな音源の話もあり、実験をしてみたいと思うこともあります。しかしながら、もともとはフレームワークの試用としてはじめたプロジェクトですのでこのくらいにしておきます。

なお、ソースコードはmbedのリポジトリで公開中です。

コメントする

This site uses Akismet to reduce spam. Learn how your comment data is processed.