「SPRESENSEでデジタルエフェクターを作ろう!」ーコンプレッサーとディストーションの実現ー [SPRESENSE]
ギターのエフェクターは次のように接続するのが一般的なようです。今回は、コンプレッサーとディストーションをまず実現したいと思います。ボリュームはアンプの抵抗定数変更やマイク入力のゲイン変更で行うことできます。
信号処理は次の本を参考にしました。音声音響信号処理の基本が身につきますのでおすすめです。

音声音響信号処理の基礎と実践 - フィルタ,ノイズ除去,音響エフェクトの原理 - (次世代信号情報処理シリーズ 2)
- 出版社/メーカー: コロナ社
- 発売日: 2021/04/08
- メディア: 単行本
■ コンプレッサーの信号処理
コンプレッサーは設定した閾値から観測信号を圧縮する処理を行います。変換を表すグラフと式を次に表します。グラフを見ても明らかなように、小さい音を持ち上げて大きな音を抑制する働きをします。
■ ノイズゲートの信号処理
ノイズを除去するフィルターとしてノイズゲートというものもあります。これはある閾値以下の値はノイズとして処理するものです。あまりやり過ぎると音が不自然になるのでこれはなくてもよいかも知れません。
■ ディストーションの信号処理
ディストーションはある閾値以上の音をクリップしてしまう処理です。ロックでは非常に重要なエフェクトですね。信号処理でゲインを与えるというやり方はありますが、情報が欠落してしまうのであまりおすすめしません。
ゲインを上げたい時はアンプのボリューム抵抗で入力信号を増幅するか、マイク入力のゲインをあげたほうがロックらしい音が得られます。
ディストーションは、あるレベルで信号をクリップしてしまうので、音のレベルを小さくしてしまうのに注意してください。出来る限りアンプの増幅度をあげてアナログ的にクリップしたほうがいいかも知れません。ここは、アナログとデジタルをうまく使い分けたいところです。
■ コンプレッサー、ノイズゲート、ディストーションの実装
これらの処理を実装してみました。前回紹介したコードの中の 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 デジタルエフェクター”
— よしのたろう 純国産ボード”スプレッセンス”応援団長(仮) (@Taro_Yoshino) May 3, 2022
コンプレッサーとディストーションを真面目に実装。
効果を確認。なかなか良い感じ。 pic.twitter.com/xpdNsyhwbK
次は空間系エフェクターの実装をしてみたいと思います。(⌒▽⌒)/~

音声音響信号処理の基礎と実践 - フィルタ,ノイズ除去,音響エフェクトの原理 - (次世代信号情報処理シリーズ 2)
- 出版社/メーカー: コロナ社
- 発売日: 2021/04/08
- メディア: 単行本

SPRESENSEではじめるローパワーエッジAI (Make: PROJECTS)
- 出版社/メーカー: オライリージャパン
- 発売日: 2022/02/28
- メディア: 単行本(ソフトカバー)

SONY SPRESENSE メインボード CXD5602PWBMAIN1
- 出版社/メーカー: スプレッセンス(Spresense)
- メディア: Tools & Hardware

SONY SPRESENSE 拡張ボード CXD5602PWBEXT1
- 出版社/メーカー: スプレッセンス(Spresense)
- メディア: Tools & Hardware
「SPRESENSEでデジタルエフェクターを作ろう!」ー低遅延入出力の実現ー [SPRESENSE]
ヤマハが行った実験によると、30ミリ秒だと遅延があると認識され、50ミリ秒以上の遅延では演奏が困難になるらしいです。

SPRESENSEはデフォルトで48000bpsで音をキャプチャしているので、最大1440サンプル(1440/48000=0.03)を1フレームとして処理できそうです。ただ、より遅延感をなくすために、その半分の720サンプル単位で処理できるようにしたいと思います。
SPRESENSEは短時間で入力から出力するためライブラリが用意されています。FrontEnd ライブラリとOutputMixerライブラリです。実装をしてみたスケッチを示します。
このスケッチのポイントは、"frontend_pcm_cb"関数です。この関数はFrontEndライブラリがデータを取得すると呼ばれるコールバック関数です。この関数の中で、入力処理→信号処理→出力処理を行います。入力がモノラル信号で出力はステレオ信号になることに注意してください。
その他見るべきポイントは" 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ミリ秒で処理ができているようです。

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

SPRESENSEではじめるローパワーエッジAI (Make: PROJECTS)
- 出版社/メーカー: オライリージャパン
- 発売日: 2022/02/28
- メディア: 単行本(ソフトカバー)

SONY SPRESENSE メインボード CXD5602PWBMAIN1
- 出版社/メーカー: スプレッセンス(Spresense)
- メディア: Tools & Hardware

SONY SPRESENSE 拡張ボード CXD5602PWBEXT1
- 出版社/メーカー: スプレッセンス(Spresense)
- メディア: Tools & Hardware
「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
ギターのエフェクターを作るには、まずエレキギターの信号をプリアンプで増幅しなければなりません。

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

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

ここで、エレキギターの音をSPRESENSEに入力するまで出来たのですが、この後のソフトウェアの処理が問題でした。ここにあるように遅延がひどい!
今日の Maker Faire Kyoto 2021 に出しそびれてしまった作品
— よしのたろう 純国産ボード”スプレッセンス”応援団長(仮) (@Taro_Yoshino) May 1, 2021
「SPRESENSE DIGITAL EFFECTER」
なかなか香ばしい作品になったのでご賞味ください
私の下手くそな素人ギターも良い味わいを加えてくれています ? pic.twitter.com/QGHqHBzwg6
それもそのはず、わかりやすく処理のパイプラインを書くと次のように組んでいました。これをどうしようかと考えていてペンディングとなりました。

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

FrontEndライブラリを使って実際にデジタルエフェクターを作ってみたのがこちらです。
#SPRESENSE でデジタルエフェクター作ってみた
— よしのたろう 純国産ボード”スプレッセンス”応援団長(仮) (@Taro_Yoshino) April 17, 2022
アンプもエフェクターも不要!しかも、エフェクトは無限大ウクレレもエフェクターかけれるぞ!
今後、メトロノーム機能も搭載予定?
では、下手くそな演奏をお楽しみください。 pic.twitter.com/RkdFONGBoA
かなりいい感じじゃないですか?これならエフェクターとして十分使えそうです。今後、これらのコードの解説をしていきたいと思います!次回をお楽しみにー♪(近日中にアップ予定)

SPRESENSEではじめるローパワーエッジAI (Make: PROJECTS)
- 出版社/メーカー: オライリージャパン
- 発売日: 2022/02/28
- メディア: 単行本(ソフトカバー)

SONY SPRESENSE メインボード CXD5602PWBMAIN1
- 出版社/メーカー: スプレッセンス(Spresense)
- メディア: Tools & Hardware

SONY SPRESENSE 拡張ボード CXD5602PWBEXT1
- 出版社/メーカー: スプレッセンス(Spresense)
- メディア: Tools & Hardware
新年度のスタート!電子工作もリスタート!! [徒然日記]
まずは昨年中途半端に終わってしまった「四月の電子工作 デジタルエフェクター」から着手しようかなと思っています。
四月の電子工作「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)
- 出版社/メーカー: オライリージャパン
- 発売日: 2022/02/28
- メディア: 単行本(ソフトカバー)

SONY SPRESENSE メインボード CXD5602PWBMAIN1
- 出版社/メーカー: スプレッセンス(Spresense)
- メディア: Tools & Hardware

SONY SPRESENSE 拡張ボード CXD5602PWBEXT1
- 出版社/メーカー: スプレッセンス(Spresense)
- メディア: Tools & Hardware

SONY SPRESENSE カメラモジュール CXD5602PWBCAM1
- 出版社/メーカー: スプレッセンス(Spresense)
- メディア: Tools & Hardware
医療費控除の申請完了! [徒然日記]
.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回マイナンバーカードを読み込む画面がありますが、その最初の画面が長いパスワードの入力画面になるので注意してください。
確定申告までまだ数日あります。作業自体はすぐに終わるので領収書を整理して申告をしてみてはどうでしょう?