SSブログ
前の5件 | -

「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 = 0; n < sample_size; ++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 = 0; n < sample_size; ++n)  {
    stereo_output[n*2] = stereo_output[n*2+1] = mono_input[n];
  }
  return true;
}



処理時間を計測してみると最大で1.6ミリ秒でした。トータル15ミリ秒ありますので、まだ処理を追加できそうですね。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!(14)  コメント(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 = 0; n < sample_size; ++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ではじめるローパワーエッジ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) 
    共通テーマ:趣味・カルチャー

    新年度のスタート!電子工作もリスタート!! [徒然日記]

    今日から新年度のスタート。昨年度は志途中で断念した電子工作活動ですができるだけ再開したいと思います。
    まずは昨年中途半端に終わってしまった「四月の電子工作 デジタルエフェクター」から着手しようかなと思っています。




    四月の電子工作「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


    あと、他にはロボットも中途半端に終わっているのでなんとかしたいところですね。モータードライバを燃やしてしまってから心が折れてしまったんですよね。やりたいことは沢山あるのですが、最近、体調もあまりよくないので、あまり無理せずやっていきたいところです。

    新年度もよろしくお願いいたします❢



    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



    SONY SPRESENSE カメラモジュール CXD5602PWBCAM1

    SONY SPRESENSE カメラモジュール CXD5602PWBCAM1

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



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

    医療費控除の申請完了! [徒然日記]

    しばらくサボっていたのですが、今年は家族全員思いの他、医者にかかることが多かったので医療費控除をしました。

    zei_kakuteishinkoku (s).png
    今回も eTax で申請をおこなったのですが、前回まで使っていた ”eTaxアプリ”から”マイナポータル”というアプリに変わっていました。”マイナポータル”いいですね。ここまで簡単に申告できるとマイナンバーカードを持つ恩恵もあるというものです。

    手順はいたって簡単。基本的には、パソコンとスマホ、マイナンバーカードと領収書を準備するだけです。PCとスマホを接続する必要もなくパソコン画面に出てくる2次元バーコードの読み込み、マイナンバーカードで認証するという手順を繰り返すだけです。

    国税庁が動画も用意しているので、それにしたがって進めるだけで申告書を送信できます。

    パソコン申告(パソコンとスマホでe-Tax送信
    https://www.youtube.com/watch?v=XsrMN19MZQA


    医療費控除の手続きは次にあります。

    パソコン申告(医療費集計フォームの使い方)
    https://www.youtube.com/watch?v=a249C5skbxg&t=0s


    ちょっと分かり難いのが、マイナンバーカードのパスワード入力画面です。マイナンバーカードにはパスワードが二つあるのですが、4桁パスワードがずっと続いたのちに長いパスワードを入力する画面が一画面だけあって、すれに気づかず当惑したところがありました。

    確定申告書の送信画面で2回マイナンバーカードを読み込む画面がありますが、その最初の画面が長いパスワードの入力画面になるので注意してください。

    確定申告までまだ数日あります。作業自体はすぐに終わるので領収書を整理して申告をしてみてはどうでしょう?




    「医療費控除」が上手にできる人 できない人

    「医療費控除」が上手にできる人 できない人

    • 出版社/メーカー: 東峰書房
    • 発売日: 2010/09/30
    • メディア: Kindle版



    nice!(20)  コメント(0) 
    共通テーマ:趣味・カルチャー
    前の5件 | -