SSブログ

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