SSブログ

「SPRESENSEでデジタルエフェクターを作ろう!」ー空間系エフェクターの実現ー [SPRESENSE]

前回、ディストーションを実装しました。今回は空間系エフェクターを実装していこうと思います。

SpresenseDigitalEffector.PNG

空間系エフェクターは最後段に接続されるエフェクターです。今回はディレイ(エコー)やリバーブを実装したいと思います。

spatial_effector.png


信号処理の理論は次の本を参考にしました。音声音響信号処理の基本が身につきますのでおすすめです。






■ ディレイ(エコー)の信号処理
ディレイは、その名の通り遅れた音を重畳する処理です。遅れた音を重ね合わせることで講堂で演奏しているような反響効果を得ることができます。

delay_effect.png

講堂のようなところで演奏すると音は、複数の壁に反響してやってきます。遅れてやってくる音はディレイ値で指定しますが、跳ね返ってくる中で減衰をしますので減衰量も指定します。減衰量は遅延量が大きいものほど大きくなります。数式では次のように表現できます。

delay_formula.png

専門的な用語になりますが、ディレイはFIR(Finite Impulse Response)フィルターで構成されています。


■ ディレイ(エコー)の実装
ディレイの実装にはメモリが必要になります。例えばディレイの量を600ミリ秒とすると、その分キャプチャした音を蓄積しておく必要があります。サンプリングレート48000Hzで1フレーム720サンプルなので1フレームあたり15ミリ秒なので40フレーム必要になります。

プログラムは前回紹介したコードの中の singnal_process 関数部分に実装しています。プログラムでは蓄積用メモリに100フレーム(1500ミリ秒分)分準備しておきました。遅延量はフレーム単位(15ミリ秒単位)で行ったほうがきれいに音が出ます。

今回は反響音はディレイ値、300ミリ秒、600ミリ秒の二つを指定します。減衰量は演算量を減らすためにそれぞれ1/2、1/4に設定しています。

void signal_process(int16_t* mono_input, int16_t* stereo_output, const uint32_t sample_size) {
  /* clean up the output buffer */
  memset(stereo_output, 0, sizeof(int16_t)*sample_size*2);

  /* memory pool for 1.5sec (= 720*100*(1/48000)) */
  static const int lines = 100;
  static int16_t src_buf[SAMPLE_SIZE*lines];  /* 2*720*100=144kBytes */

  /* shift the buffer data in src_buf and add the latest data to top of the bufer */
  memcpy(&src_buf[0], &src_buf[SAMPLE_SIZE], SAMPLE_SIZE*sizeof(int16_t)*(lines-1)); 
  memcpy(&src_buf[(lines-1)*SAMPLE_SIZE], &mono_input[0], SAMPLE_SIZE*sizeof(int16_t));


  /* set constatns for echo effect */
  static const uint32_t D1_in_ms = 300; /* milli sec */
  static const uint32_t D2_in_ms = 600; /* milli sec */
  static const uint32_t offset1 = D1_in_ms * 48000 / 1000;
  static const uint32_t offset2 = D2_in_ms * 48000 / 1000;

  const int src_buf_end_point = lines*SAMPLE_SIZE-1;
  for (int n = SAMPLE_SIZE-1; n >= 0; --n) {
    /* set h1 = 1/2, h2 = 1/4 to reduce calculation costs */
    mono_input[(SAMPLE_SIZE-1)-n] = src_buf[src_buf_end_point-n] + src_buf[src_buf_end_point-n-offset1]/2 + src_buf[src_buf_end_point-n-offset2]/4;
  }
  
  /* copy the signal to output buffer */
  for (int n = SAMPLE_SIZE-1; n >= 0; --n)  {
    stereo_output[n*2] = stereo_output[n*2+1] = mono_input[n];
  }
  return;
}


処理時間は約240マイクロ秒。ほぼ無視できるレベルです。メモリは消費しますが、計算量はたいしたことないので想定通りです。


■ リバーブの信号処理
リバーブは出力したディレイと同じく音の反響処理になります。ディレイは原音が反響して返ってくる処理に対し、リバーブは処理後の音が返ってくるのが異なります。ちょうど、”やまびこ”のようなイメージをもつといいかも知れません。

reverb_effect.png

リバーブは反響して遅れてやってきた音を原音に加えるのはディレイと同じなのですが、遅れた音は原音ではなく出力後の音を加えます。数式で表すと次のようになります。

reverb_formula.png

専門的な用語になりますが、リバーブはIIR(Infinite Impulse Response)フィルターで構成されています。


■ リバーブの実装
リバーブの実装にはメモリが必要になります。蓄積する音は、ディレイと異なり、キャプチャした原音ではなく、処理後の音になるので注意してください。

プログラムは前回紹介したコードの中の singnal_process 関数部分に実装しています。プログラムでは蓄積用メモリに100フレーム(1500ミリ秒分)分準備しておきました。遅延量はフレーム単位(15ミリ秒単位)で行ったほうがきれいに音が出ます。

遅延量は600ミリ秒。減衰量は演算量を減らすために1/2に設定しました。

void signal_process(int16_t* mono_input, int16_t* stereo_output, const uint32_t sample_size) {
  /* clean up the output buffer */
  memset(stereo_output, 0, sizeof(int16_t)*sample_size*2);

  /* memory pool for 1.5sec (= 720*100*(1/48000)) */
  static const int lines = 100;
  static int16_t out_buf[SAMPLE_SIZE*lines];  /* 2*720*100=144kBytes */

  /* set constatns for echo effect */
  static const uint32_t D_in_ms = 600; /* milli sec */
  static const uint32_t offset = D_in_ms * 48000 / 1000;
   
  const int src_buf_end_point = lines*SAMPLE_SIZE-1;
  for (int n = SAMPLE_SIZE-1; n >= 0; --n) {
    /* set alpha = 1/2 to reduce calculation costs */
    mono_input[(SAMPLE_SIZE-1)-n] =  mono_input[(SAMPLE_SIZE-1)-n] + out_buf[src_buf_end_point-n-offset]/2; 
  }

  /* shift the buffer data in src_buf and add the latest data to top of the bufer */
  memcpy(&out_buf[0], &out_buf[SAMPLE_SIZE], SAMPLE_SIZE*sizeof(int16_t)*(lines-1)); 
  memcpy(&out_buf[(lines-1)*SAMPLE_SIZE], &mono_input[0], SAMPLE_SIZE*sizeof(int16_t));
  
  /* copy the signal to output buffer */
  for (int n = SAMPLE_SIZE-1; n >= 0; --n)  {
    stereo_output[n*2] = stereo_output[n*2+1] = mono_input[n];
  }
  return;
}


こちらも処理時間は約240マイクロ秒。ほぼ無視できるレベルですね。


■ 処理の確認
実際の効果を確認してみました。なかなかそれらしい音を出せました。ディレイやリバーブのエフェクターの価格を見るとそれぞれ2万円くらいなので、SPRESENSEで実現できればコストパフォーマンスは高そうです。音作りも自分で自由にできるので、ギターの楽しみ方が広がりそうですね。




次は、モジュレーション系のエフェクターであるビブラートやトレモロを実装してみたいと思います。
(^^)/~


「SPRESENSEでデジタルエフェクターを作ろう!」ープリアンプの製作編

「SPRESENSEでデジタルエフェクターを作ろう!」ー低遅延入出力の実現ー

「SPRESENSEでデジタルエフェクターを作ろう!」ーコンプレッサーとディストーションの実現ー

「SPRESENSEでデジタルエフェクターを作ろう!」ー空間系エフェクターの実現ー

「SPRESENSEでデジタルエフェクターを作ろう!」ーモジュレーション系エフェクターの実現ー






SPRESENSEではじめるローパワーエッジAI (Make: PROJECTS)

SPRESENSEではじめるローパワーエッジAI (Make: PROJECTS)

  • 出版社/メーカー: オライリージャパン
  • 発売日: 2022/02/28
  • メディア: 単行本(ソフトカバー)



SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

  • 出版社/メーカー: スプレッセンス(Spresense)
  • メディア: Tools & Hardware



SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

  • 出版社/メーカー: スプレッセンス(Spresense)
  • メディア: Tools & Hardware



nice!(16)  コメント(0) 
共通テーマ:趣味・カルチャー

「SPRESENSEでデジタルエフェクターを作ろう!」ーコンプレッサーとディストーションの実現ー [SPRESENSE]

いよいよ今回から、デジタルエフェクターらしくギターの生音に信号処理を加えていきます。


SpresenseDigitalEffector.PNG


ギターのエフェクターは次のように接続するのが一般的なようです。今回は、コンプレッサーとディストーションをまず実現したいと思います。ボリュームはアンプの抵抗定数変更やマイク入力のゲイン変更で行うことできます。


EffectorConnection.PNG


信号処理は次の本を参考にしました。音声音響信号処理の基本が身につきますのでおすすめです。







■ コンプレッサーの信号処理

コンプレッサーは設定した閾値から観測信号を圧縮する処理を行います。変換を表すグラフと式を次に表します。グラフを見ても明らかなように、小さい音を持ち上げて大きな音を抑制する働きをします。


Compressor.PNG


■ ノイズゲートの信号処理

ノイズを除去するフィルターとしてノイズゲートというものもあります。これはある閾値以下の値はノイズとして処理するものです。あまりやり過ぎると音が不自然になるのでこれはなくてもよいかも知れません。


NoiseGate.PNG


■ ディストーションの信号処理

ディストーションはある閾値以上の音をクリップしてしまう処理です。ロックでは非常に重要なエフェクトですね。信号処理でゲインを与えるというやり方はありますが、情報が欠落してしまうのであまりおすすめしません。

ゲインを上げたい時はアンプのボリューム抵抗で入力信号を増幅するか、マイク入力のゲインをあげたほうがロックらしい音が得られます。


Distortion.PNG

ディストーションは、あるレベルで信号をクリップしてしまうので、音のレベルを小さくしてしまうのに注意してください。出来る限りアンプの増幅度をあげてアナログ的にクリップしたほうがいいかも知れません。ここは、アナログとデジタルをうまく使い分けたいところです。


■ コンプレッサー、ノイズゲート、ディストーションの実装

これらの処理を実装してみました。前回紹介したコードの中の singnal_process 関数部分だけを掲載します。


void signal_process(int16_t* mono_input, int16_t* stereo_output, uint32_t sample_size) {

  /* clean up the output buffer */
  memset(stereo_output, 0, sizeof(int16_t)*sample_size*2);

  /* volume (gain setting) */
  static int16_t gain = 0; /* 0dB */
  theFrontEnd->setMicGain(gain);
  
  /* compressor & distotin*/
  static int16_t comp_th = 0x7fff/128;
  static int16_t noise_g = 0x0005;
  static int16_t dist_th = 0x7fff/32;
  static float alpha = 0.5;
  for (int n = SAMPLE_SIZE-1; n >= 0; --n) {
    /* compressor process */
    if (abs(mono_input[n]) > comp_th) {
      int32_t value = mono_input[n];
      mono_input[n] = value/abs(value)*(abs(value)*alpha + (1.-alpha)*comp_th);
    }
    /* noise gate process */
    if (abs(mono_input[n]) < noise_g) {
      mono_input[n] = 0;
    }
    /* distortion process */
    if (abs(mono_input[n]) > dist_th) {
      int32_t value = mono_input[n];
      mono_input[n] = value/abs(value)*dist_th;
    }   
  }

  /* copy the signal to output buffer */
    for (int n = SAMPLE_SIZE-1; n >= 0; --n) {
    stereo_output[n*2] = stereo_output[n*2+1] = mono_input[n];
  }

  return;
}



処理時間を計測してみると最大で1.6ミリ秒でした。トータル15ミリ秒ありますので、まだ処理を追加できそうですね。SPRESENSEの計算能力恐るべしです。


■ 効果を確認

実際の効果を確認してみました。なかなかそれらしい音を出せました。全部プログラムで処理しているので、自分好みの音を自分で作れるのが醍醐味ですね。






次は空間系エフェクターの実装をしてみたいと思います。(⌒▽⌒)/~


「SPRESENSEでデジタルエフェクターを作ろう!」ープリアンプの製作編

「SPRESENSEでデジタルエフェクターを作ろう!」ー低遅延入出力の実現ー

「SPRESENSEでデジタルエフェクターを作ろう!」ーコンプレッサーとディストーションの実現ー

「SPRESENSEでデジタルエフェクターを作ろう!」ー空間系エフェクターの実現ー

「SPRESENSEでデジタルエフェクターを作ろう!」ーモジュレーション系エフェクターの実現ー






SPRESENSEではじめるローパワーエッジAI (Make: PROJECTS)

SPRESENSEではじめるローパワーエッジAI (Make: PROJECTS)

  • 出版社/メーカー: オライリージャパン
  • 発売日: 2022/02/28
  • メディア: 単行本(ソフトカバー)



SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

  • 出版社/メーカー: スプレッセンス(Spresense)
  • メディア: Tools & Hardware



SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

  • 出版社/メーカー: スプレッセンス(Spresense)
  • メディア: Tools & Hardware




nice!(16)  コメント(0) 
共通テーマ:趣味・カルチャー