SSブログ
前の5件 | -

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

今回はエフェクター系統の最後のエフェクター、モジュレーション系エフェクターを実装していきます。

SpresenseDigitalEffector.PNG

空間系エフェクターは歪み系と空間系エフェクターの中間に接続されるエフェクータです。今回はトレモロ、ビブラート、コーラスを実装したいと思います。モジュレーション系エフェクターは正弦波で変調を加えているのが大きな特徴です。

effector_connection.png

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






■ トレモロの信号処理

トレモロはモジュレーション系エフェクターの中で最も簡単な処理です。正弦波によって音の強弱をかえていきます。うねりのような効果が得られますが、エフェクターとしての効果は控えめな印象です。

Toremoro.png

dは通常0.3~0.6の値、周波数は数Hzに設定することが多いようです。曲にあわせて調整が必要になります。


■ トレモロの実装

正弦波の処理は時間がかかるのでCoretex M4F の浮動小数点アクセラレータを使うため、ARMが提供している数値ライブラリを用いました。実装では正弦波の周波数を4Hzで、dは0.4を設定しました。
(コードの中では、SAMPLE_SIZE と sample_size が混在していますが同じものです。そのうち整理します)

#define ARM_MATH_CM4
#define __FPU_PRESENT 1U
#include <cmsis/arm_math.h>

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));

  static int theta = 0;
  const int mod_freq = 4; /* Hz */ 
  const float omega = 2.*PI*mod_freq/AS_SAMPLINGRATE_48000;
  const int sin_total_samples = AS_SAMPLINGRATE_48000/mod_freq;
  const float d = 0.4; /* amplitude of moduration */
  
  const int src_buf_end_point = lines*SAMPLE_SIZE-1;
  for (int n = SAMPLE_SIZE-1; n >= 0; --n) {
    float modulation = 1.-d*arm_sin_f32(omega*theta);
    mono_input[(SAMPLE_SIZE-1)-n] = (int16_t)(modulation*src_buf[src_buf_end_point-n]);
    if (++theta >= sin_total_samples) theta = 0;
  }
  
  /* 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;
}



製品の中には正弦波の他に三角波、矩形波を加える機能をもっているものもあるようです。プログラミングできますので、そのような機能をもたせてみるのも楽しいかもしれません。


■ ビブラートの信号処理

ビブラートはディレイと同じように音を遅延させる処理になります。ただし、その遅延量は、正弦波で与えます。トレモロと異なりエフェクトの効果は抜群でギターの音がシンセのような音に変わります。ただ、演奏するには慣れが必要そうです。

Vibrato.png

dは遅延量を設定するもので、0~2d の間で変化します。ここはサンプルで指定しますが、時間に換算するには、サンプリング周波数の逆数をサンプル数でかけた数になります。正弦波の周波数は小数点以下から数Hzに設定することが多いようです。


■ ビブラートの実装

ビブラートの変数 d には 96サンプル、2ミリ秒の遅延を与えます。音の遅延は0~4ミリ秒で変化します。また、周波数はトレモロと同じく4Hzに設定しました。

#define ARM_MATH_CM4
#define __FPU_PRESENT 1U
#include <cmsis/arm_math.h>

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));

  const float mod_freq = 4; /* Hz */ 
  const float omega = 2.*PI*mod_freq/AS_SAMPLINGRATE_48000;
  const int sin_total_samples = AS_SAMPLINGRATE_48000/mod_freq;
  const int d = 96; /* delay samples = delay time / Sampling Rate */
  static int theta = 0;

  const int src_buf_end_point = lines*SAMPLE_SIZE-1;
  for (int n = SAMPLE_SIZE-1; n >= 0; --n) {
    float modulation = 1. + arm_sin_f32(omega*theta);
    int offset = (int)(modulation*d);
    mono_input[(SAMPLE_SIZE-1)-n] = src_buf[src_buf_end_point-n-offset];
    if (++theta == sin_total_samples) theta = 0;
  }

  /* 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;
}



■ コーラスの信号処理

コーラスの信号処理は、ビブラートの変形になります。エコーと同じく遅延した音をもとの音に重ね合わせます。コーラスの効果は、ビブラートのような機械的な音ではなく、自然な音が出せるのでいろいろな場面で使えそうです。

コーラスはその名の通り、声の重ね合わせを表現するので、遅延料 d は数十ミリ秒と比較的大きな値を与え、周波数は小数点以下の値を与えることが多いようです。

Chorus.png



■ コーラスの実装

コーラスの遅延量 d には480サンプル、100ミリ秒を与えます。また周波数は 0.1Hz に設定しました。かなり自然な音に仕上がっていると思います。

#define ARM_MATH_CM4
#define __FPU_PRESENT 1U
#include <cmsis/arm_math.h>

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));

  const float mod_freq = 0.1; /* Hz */ 
  const float omega = 2.*PI*mod_freq/AS_SAMPLINGRATE_48000;
  const int sin_total_samples = AS_SAMPLINGRATE_48000/mod_freq;
  const int d = 720*2; /* delay samples = 720(samples/frame) * delay time (msec) / 15 (msec/frame) */
  const int p = 480;   /* p must be less than d */
  static int theta = 0;

  const int src_buf_end_point = lines*SAMPLE_SIZE-1;
  for (int n = SAMPLE_SIZE-1; n >= 0; --n) {
    int offset = (int)(d + p*arm_sin_f32(omega*theta));
    mono_input[(SAMPLE_SIZE-1)-n] = (src_buf[src_buf_end_point-n] + src_buf[src_buf_end_point-n-offset])/2;
    if (++theta == sin_total_samples) theta = 0;
  }

  /* 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;
}




■ 処理の確認

実際の効果を確認してみました。ビブラートの音はかなりエグいですね。SFやホラーの曲で使うと効果がありそうです。プロの演奏を見るとすごく効果的に使っているので関心します。ギターど素人の私には到底ムリなレベル。ペダルでオンオフ、効果の変化を与えられると効果的に使えそうですね。ギター練習しようかな…

今後、簡単に使えるように Arduino のライブラリにまとめていきたいと思います!





「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

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

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!(13)  コメント(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!(15)  コメント(0) 
共通テーマ:趣味・カルチャー

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

SPRESENSEでデジタルエフェクターを実現するには、エレキギターの音をキャプチャーして信号処理をして出力するまで短時間で行う必要があります。でないと楽器とスピーカーの音が大きくずれてしまい、まともな演奏ができません。

ヤマハが行った実験によると、30ミリ秒だと遅延があると認識され、50ミリ秒以上の遅延では演奏が困難になるらしいです。


enso-chien.png


SPRESENSEはデフォルトで48000bpsで音をキャプチャしているので、最大1440サンプル(1440/48000=0.03)を1フレームとして処理できそうです。ただ、より遅延感をなくすために、その半分の720サンプル単位で処理できるようにしたいと思います。

SPRESENSEは短時間で入力から出力するためライブラリが用意されています。FrontEnd ライブラリとOutputMixerライブラリです。実装をしてみたスケッチを示します。

このスケッチのポイントは、"frontend_pcm_cb"関数です。この関数はFrontEndライブラリがデータを取得すると呼ばれるコールバック関数です。この関数の中で、入力処理→信号処理→出力処理を行います。入力がモノラル信号で出力はステレオ信号になることに注意してください。

  • 入力処理:"frontend_signal_input"関数

  • 信号処理:"signal_process"関数

  • 出力処理:"mixer_stereo_output"関数


  • その他見るべきポイントは" setup"関数と"loop"関数です。setup関数で FroneEndライブラリとOuputMixerライブラリの初期化。loop関数でエラー状態の監視を行っています。


    #include <FrontEnd.h>
    #include <OutputMixer.h>
    #include <MemoryUtil.h>
    #include <arch/board/board.h>
    
    #define SAMPLE_SIZE (720)
    
    FrontEnd *theFrontEnd;
    OutputMixer *theMixer;
    
    const int32_t channel_num = AS_CHANNEL_MONO;
    const int32_t bit_length  = AS_BITLENGTH_16;
    const int32_t sample_size = SAMPLE_SIZE;
    const int32_t frame_size  = sample_size * (bit_length / 8) * channel_num;
    bool isErr = false;
    
    void frontend_attention_cb(const ErrorAttentionParam *param) {
      Serial.println("ERROR: Attention! Something happened at FrontEnd");
      if (param->error_code >= AS_ATTENTION_CODE_WARNING) isErr = true;
    }
    
    void mixer_attention_cb(const ErrorAttentionParam *param){
      Serial.println("ERROR: Attention! Something happened at Mixer");
      if (param->error_code >= AS_ATTENTION_CODE_WARNING) isErr = true;
    }
    
    static bool frontend_done_cb(AsMicFrontendEvent ev, uint32_t result, uint32_t detail){
      UNUSED(ev);  UNUSED(result);  UNUSED(detail);  
      return true;
    }
    
    static void outputmixer_done_cb(MsgQueId requester_dtq, MsgType reply_of, AsOutputMixDoneParam* done_param) {
      UNUSED(requester_dtq);  UNUSED(reply_of);  UNUSED(done_param);
      return;
    }
    
    static void outputmixer0_send_cb(int32_t identifier, bool is_end) {
      UNUSED(identifier);  UNUSED(is_end);
      return;
    }
    
    static void frontend_pcm_cb(AsPcmDataParam pcm) {
      static uint8_t mono_input[frame_size];
      static uint8_t stereo_output[frame_size*2];  
      static const bool time_measurement = true;
      if (time_measurement) {
        static uint32_t last_time = 0;
        uint32_t current_time = micros();
        uint32_t duration = current_time - last_time;
        last_time = current_time;
        Serial.println("duration = " + String(duration));
      }
    
      frontend_signal_input(pcm, mono_input, frame_size);
      signal_process((int16_t*)mono_input, (int16_t*)stereo_output, sample_size);
      mixer_stereo_output(stereo_output, frame_size);
      return;
    }
    
    void frontend_signal_input(AsPcmDataParam pcm, uint8_t* input, uint32_t frame_size) {
      /* clean up the input buffer */
      memset(input, 0, frame_size);
    
      if (!pcm.is_valid) {
        Serial.println("WARNING: Invalid data! @frontend_signal_input");
        return;
      }
       
      if (pcm.size > frame_size) {
        Serial.print("WARNING: Captured size is too big! -");
        Serial.print(String(pcm.size));
        Serial.println("- @frontend_signal_input");
        pcm.size = frame_size;
      } 
      
      /* copy the signal to signal_input buffer */
      if (pcm.size != 0) {
        memcpy(input, pcm.mh.getPa(), pcm.size);
      } else {
        Serial.println("WARNING: Captured size is zero! @frontend_signal_input");
      }  
    }
    
    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);
      /* 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 true;
    }
    
    void mixer_stereo_output(uint8_t* stereo_output, uint32_t frame_size) {
      
      /* Alloc MemHandle */
      AsPcmDataParam pcm_param;
      if (pcm_param.mh.allocSeg(S0_REND_PCM_BUF_POOL, frame_size) != ERR_OK) {
        Serial.println("ERROR: Cannot allocate memory @mixer_stereo_output");
        isErr = false;
        return;
      }
      
      /* Set PCM parameters */
      pcm_param.is_end = false;
      pcm_param.identifier = OutputMixer0;
      pcm_param.callback = 0;
      pcm_param.bit_length = bit_length;
      pcm_param.size = frame_size*2;
      pcm_param.sample = frame_size;
      pcm_param.is_valid = true;
      memcpy(pcm_param.mh.getPa(), stereo_output, pcm_param.size);
     
      int err = theMixer->sendData(OutputMixer0, outputmixer0_send_cb, pcm_param);
      if (err != OUTPUTMIXER_ECODE_OK) {
        Serial.println("ERROR: sendData -" + String(err) + "- @mixer_stereo_output");
        isErr = true;
      }
    }
    
    void setup() {
      Serial.begin(115200);
    
      /* Initialize memory pools and message libs */
      initMemoryPools();
      createStaticPools(MEM_LAYOUT_RECORDINGPLAYER);
    
      /* setup FrontEnd and Mixer */
      theFrontEnd = FrontEnd::getInstance();
      theMixer = OutputMixer::getInstance();
    
      /* begin FrontEnd and OuputMixer */
      theFrontEnd->begin(frontend_attention_cb);
      theMixer->begin();
      Serial.println("Setup: FrontEnd and OutputMixer began");
    
      /* activate FrontEnd and Mixer */
      theFrontEnd->setCapturingClkMode(FRONTEND_CAPCLK_NORMAL);
      theFrontEnd->activate(frontend_done_cb);
      theMixer->create(mixer_attention_cb);
      theMixer->activate(OutputMixer0, outputmixer_done_cb);
      delay(100); /* waiting for Mic startup */
      Serial.println("Setup: FrontEnd and OutputMixer activated");
    
      /* Initialize FrontEnd */
      AsDataDest dst;
      dst.cb = frontend_pcm_cb;
      theFrontEnd->init(channel_num, bit_length, sample_size, AsDataPathCallback, dst);
      Serial.println("Setup: FrontEnd initialized");
    
      /* Set rendering volume */
      theMixer->setVolume(0, 0, 0);
    
      /* Unmute */
      board_external_amp_mute_control(false);
      theFrontEnd->start();
      Serial.println("Setup: FrontEnd started");
    }
    
    void loop() {
      if (isErr == true) {
        board_external_amp_mute_control(true); 
        theFrontEnd->stop();
        theFrontEnd->deactivate();
        theMixer->deactivate(OutputMixer0);
        theFrontEnd->end();
        theMixer->end();
        Serial.println("Capturing Process Terminated");
        while(1) {};
      }
    }
    
    


    入出力までの時間の計測結果は次のようになりました。縦軸はマイクロ秒です。多少ばらつきはありますが、狙った通り15ミリ秒で処理ができているようです。

    Duration.png


    エフェクトは、"signal_process"関数内に記述していけば出力に反映されます。次から入力信号にエフェクトをかけてみたいと思います!
    (^^)/~

    「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!(11)  コメント(0) 
    共通テーマ:趣味・カルチャー

    「SPRESENSEでデジタルエフェクターを作ろう!」ーSPRESENSEアンプ編ふたたびー [SPRESENSE]

    昨年の今頃、SPRESENSEでデジタルエフェクターを作るチャレンジをしていましたが、仕事が忙しくなりペンディングをしていました。ようやく仕事に余裕が出てきたので検討を再開することにしました!

    詳しくは過去記事を参照していただきたいのですが、軽く今までのおさらいをしたいと思います。


    「SPRESENSEでデジタルエフェクターを作ろう!」ープリアンプの製作編ー
    https://makers-with-myson.blog.ss-blog.jp/2021-04-19

    「SPRESENSEでデジタルエフェクターを作ろう!」ーSPRESENSEアンプ編ー
    https://makers-with-myson.blog.ss-blog.jp/2021-05-03


    ギターのエフェクターを作るには、まずエレキギターの信号をプリアンプで増幅しなければなりません。


    SpresenseDigitalEffector.png


    このプリアンプはオペアンプを使って制作しました。抵抗やコンデンサーの定数の決定方法は、過去の記事を参照してください。


    SpresensePreAmp.png


    実装は次のようにしました。


    プリアンプ.png


    ここで、エレキギターの音をSPRESENSEに入力するまで出来たのですが、この後のソフトウェアの処理が問題でした。ここにあるように遅延がひどい!





    それもそのはず、わかりやすく処理のパイプラインを書くと次のように組んでいました。これをどうしようかと考えていてペンディングとなりました。


    DigitalEffector_Pipeline1.png


    この度、検討を再開していてFrondEndライブラリがMixerと直結できることが分かりました。パイプラインの図で書くと次のようになります。これだとほぼ遅延はありません。理論的には15ミリ秒程度の遅延に収まるはずです。人には感知できないレベルです。


    DigitalEffector_Pipeline2.png


    FrontEndライブラリを使って実際にデジタルエフェクターを作ってみたのがこちらです。




    かなりいい感じじゃないですか?これならエフェクターとして十分使えそうです。今後、これらのコードの解説をしていきたいと思います!次回をお楽しみにー♪(近日中にアップ予定)




    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!(14)  コメント(0) 
    共通テーマ:趣味・カルチャー
    前の5件 | -