SSブログ

SPRESENSE(SORA-Q)が月面撮影成功!!歴史的偉業を達成! [SPRESENSE]

SPRESENSEがやってくれました。SPRESENSEを搭載した、JAXAの月面探査機SLIMの子探査機のLEV-2(SORA-Q)が月面走行ならびに月面上に着陸したSLIMの撮影に成功しました!すごい!!




SONY SPRESENSE メインボード CXD5602PWBMAIN1

SONY SPRESENSE メインボード CXD5602PWBMAIN1

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




SLIMそのものは、50m地点でスラスタ一つが脱落。異常モードに入り、本来、高度1.8mで放出する予定だった LEV-1、LEV-2 を高度 5m で放出。それでも両方とも問題なく動作したのはすごいですね。




スラスタを失ったSLIMは横滑りしながら月面に着陸し、横方向の勢いがあまってしまい写真のように逆立ちをしてしまったようです。




太陽光パネルが西方向を向いており、月面の太陽は東側。SLIMは電源を確保できずに現在停止しています。月の昼間はだいたい17日くらい。1月末には天頂に来て、2月上旬には西に来るので、そのときに復活できるかもしれません。




一方、LEV-1もすごいですね。飛び跳ねて移動するというのもユニークですが、あの小さなロボットで地球との通信も実現しています。通信はテレメトリにUHF帯、データ送信用にS帯を使っているようです。




残念ながら、LEV-1 の画像取得はうまくいっていないようですが、それでもLEV-1、LEV-2で複数のロボットが連携して月面で動作させたのは世界初!ロボット大国の面目躍如ですね。

最後に特筆すべきは、SLIMのピンポイント着陸でしょう。画像照合しながら完全自立制御。スラスタが落ちなければ3−5m精度だったようです。

今までは 数kmから十数km レベルです。というのも、月面着陸直前の探査機の速度は時速200km程度。それを急減速させて着陸させるので、それくらいの誤差が出るのは当然だと思われました。それをピッタリ狙った駐車場つけることができたのが今回の偉業です。



最近は失敗続きの日本の宇宙開発ですが、ひさしぶりに大きなインパクトの偉業を成し遂げました。今後、日本の月面開発が活発化しそうですね。


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

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

「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) 
    共通テーマ:趣味・カルチャー

    Tensorflow対応 SPRESENSE Arduinoボードパッケージをネットからインストール [SPRESENSE]

    SPRESENSE Arduino ボードパッケージをネットからインストールできるようにしてみました。思ったよりも簡単にできたので、皆さんもSpresense SDKから独自のArduinoパッケージを作ってみてはいかがでしょう?インストールの仕方はQiitaにまとめています。


    SPRESENSEのTensorflow対応Arduinoボードパッケージを公開してみた
    Qiita.png
    https://qiita.com/TaroYoshino/items/25255ada7a4fb6e4a788


    独自のArduinoボードパッケージにするポイントは、spresense-arduino-compatible のMakefileと出力した package_spresense_tensorflow_index.jsonを変更します。

    Makefile の変更
    15行目の NAME_SUFFIX を変更します。

    NAME_SUFFIX       ?= tensorflow
    


    package_spresense_tensorflow_index.jsonの変更
    outディレクトリに出力したファイルをgithubにアップロードして、Arduino Library、SDK、tools がダウンロードできるように package_spresense_tensorflow_index.json のURLを変更します。

    ・ArduinoパッケージURLの変更
          "maintainer": "Spresense tensorflow Community",
          "name": "SPRESENSE_tensorflow",
          "platforms": [
            {
              "architecture": "spresense",
              "archiveFileName": "spresense-v2.4.1_tensorflow.tar.gz",
              "boards": [
                {
                  "name": "Generic Spresense Module"
                }
              ],
              "category": "SPRESENSE",
              "checksum": "SHA-256:4cd54064112ffcd92a886c372c48b4c6b81dd4f2c7b259f31d948c5eddf91f47",
              "help": {
                "online": "https://developer.sony.com/develop/spresense/developer-tools/get-started-using-arduino-ide/set-up-the-arduino-ide"
              },
              "name": "Spresense tensorflow Board",
              "size": 952491,
              "toolsDependencies": [
                {
                  "name": "spresense-sdk",
                  "packager": "SPRESENSE_tensorflow",
                  "version": "2.4.1"
                },
                {
                  "name": "spresense-tools",
                  "packager": "SPRESENSE_tensorflow",
                  "version": "2.4.1"
                },
                {
                  "name": "gcc-arm-none-eabi",
                  "packager": "SPRESENSE",
                  "version": "5.4.1"
                }
              ],
              "url": "https://github.com/YoshinoTaro/spresense-arduino-tensorflow/raw/main/staging/packages/spresense-v2.4.1_tensorflow.tar.gz",
              "version": "2.4.1"
            }
    


    ・SDKパッケージのURL変更
            {
              "name": "spresense-sdk",
              "systems": [
                {
                  "archiveFileName": "spresense-sdk-v2.4.1_tensorflow.tar.gz",
                  "checksum": "SHA-256:54f269c49daca0382b7d5dc5bdbf04442511b9b9be2663fdae14231fd7993667",
                  "host": "i686-mingw32",
                  "size": 23265440,
                  "url": "https://github.com/YoshinoTaro/spresense-arduino-tensorflow/raw/main/staging/packages/spresense-sdk-v2.4.1_tensorflow.tar.gz"
                },
                {
                  "_comment": "Allow x64-Linux build",
                  "archiveFileName": "spresense-sdk-v2.4.1_tensorflow.tar.gz",
                  "checksum": "SHA-256:54f269c49daca0382b7d5dc5bdbf04442511b9b9be2663fdae14231fd7993667",
                  "host": "x86_64-pc-linux-gnu",
                  "size": 23265440,
                  "url": "https://github.com/YoshinoTaro/spresense-arduino-tensorflow/raw/main/staging/packages/spresense-sdk-v2.4.1_tensorflow.tar.gz"
                },
                {
                  "archiveFileName": "spresense-sdk-v2.4.1_tensorflow.tar.gz",
                  "checksum": "SHA-256:54f269c49daca0382b7d5dc5bdbf04442511b9b9be2663fdae14231fd7993667",
                  "host": "i386-apple-darwin11",
                  "size": 23265440,
                  "url": "https://github.com/YoshinoTaro/spresense-arduino-tensorflow/raw/main/staging/packages/spresense-sdk-v2.4.1_tensorflow.tar.gz"
                }
              ],
              "version": "2.4.1"
            },
    


    ・toolsパッケージの変更
            {
              "name": "spresense-tools",
              "systems": [
                {
                  "archiveFileName": "spresense-tools-v2.4.1_tensorflow.tar.gz",
                  "checksum": "SHA-256:7d90721932a9f4ce27ceddead0d14b8b53f9e14168e3f1c8844a7425a770e5c3",
                  "host": "i686-mingw32",
                  "size": 59772452,
                  "url": "https://github.com/YoshinoTaro/spresense-arduino-tensorflow/raw/main/staging/packages/spresense-tools-v2.4.1_tensorflow.tar.gz"
                },
                {
                  "_comment": "Allow x64-Linux build",
                  "archiveFileName": "spresense-tools-v2.4.1_tensorflow.tar.gz",
                  "checksum": "SHA-256:7d90721932a9f4ce27ceddead0d14b8b53f9e14168e3f1c8844a7425a770e5c3",
                  "host": "x86_64-pc-linux-gnu",
                  "size": 59772452,
                  "url": "https://github.com/YoshinoTaro/spresense-arduino-tensorflow/raw/main/staging/packages/spresense-tools-v2.4.1_tensorflow.tar.gz"
                },
                {
                  "archiveFileName": "spresense-tools-v2.4.1_tensorflow.tar.gz",
                  "checksum": "SHA-256:7d90721932a9f4ce27ceddead0d14b8b53f9e14168e3f1c8844a7425a770e5c3",
                  "host": "i386-apple-darwin11",
                  "size": 59772452,
                  "url": "https://github.com/YoshinoTaro/spresense-arduino-tensorflow/raw/main/staging/packages/spresense-tools-v2.4.1_tensorflow.tar.gz"
                }
              ],
              "version": "2.4.1"
            }
    



    以上の対応をすれば、Arduino IDEでボードパッケージ(package_spresense_tensorflow_index.json)のURLを指定すれば、ダウンロードができるようになります。注意するポイントとしては、GitHubのRawデータのURLを設定するところです。かなり備忘録に近い内容になってしまいましたが、独自のArduinoパッケージを作るヒントになれば幸いです。



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

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

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



    SONY SPRESENSE メインボード CXD5602PWBMAIN1

    SONY SPRESENSE メインボード CXD5602PWBMAIN1

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



    SONY SPRESENSE LTE拡張ボード CXD5602PWBLM1JUL

    SONY SPRESENSE LTE拡張ボード CXD5602PWBLM1JUL

    • 出版社/メーカー: Spresense
    • メディア:



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

    Tensorflow対応のSPRESENSE Arduinoパッケージを作ってみた話 [SPRESENSE]

    SPRESENSEのSDKではTensorflow lite/microが利用できるのですが、ArduinoIDEでなんとか利用できないかなとずっと考えていたのですが、ようやくそれを実現しました。手順については、Qiitaにまとめていますので、ぜひ参照してください。


    Tensorflow lite/micro 用の SPRESENSE Arduino パッケージを作ってみた
    Qiita.png
    https://qiita.com/TaroYoshino/items/be62c70ad78a9a80308a


    今回はその経緯についてちょっと触れたいと思います。今後、作業をしてみる人の参考になるかもしれません。


    コンパイルを通すのに一苦労
    外部ライブラリをArduinoパッケージに取り込むのは、jpegライブラリを追加したときに経験済なので、ある程度どのあたりを変えたらよいのかというあたりはついていました。

    参考
    SPRESENSE Arduino で libjpeg を使いたい
    stackoverflow.png
    https://ja.stackoverflow.com/questions/73301/spresense-arduino-%e3%81%a7-libjpeg-%e3%82%92%e4%bd%bf%e3%81%84%e3%81%9f%e3%81%84


    しかし、Tensorflow lite/microの場合は大変でした。もっとも大変だったのはヘッダーファイルのエクスポートです。とても整理されたコードとは思えず、依存関係が複雑でかつフォルダが細分化されているので、必要なすべてのヘッダーファイルを絞り出すのにかなりの時間がかかりました。


    サンプルコードがとにかく分かりにくい
    一通りコンパイルは通ったので、動作確認のためにサンプルコードをArduinoに移植しようとしたのですが、サンプルなのにすごく分かりにくい。プラットフォームに依存しないように作ろうとしたことはわかりますが定数まで、分離する必要あるんですかね?

    サンプルという以上は普通はわかりやすい例を出すものですが、とてもそのような事を意識したようには思えません。HelloWorldサンプルがなぜ正弦波を予測して出力するものなのか意味わかりません。Androidは非常にわかりやすく作られていましたが、Tensorflowはひどいですね。やはり組み込みはおまけなのでしょうか?


    サンプルはビルドしたけどTensorflowの学習済モデルが動かない
    どうにかこうにか、HelloWorldサンプルをArduinoに移植し、SPRESENSEのイメージを作るところまではいきました。しかし、それを書き込んでみると、Tensorflowの学習済モデルが動いている気配がない。量子化係数がnanを返しているので、インタープリターが動いていない。

    理由がわからず試行錯誤を繰り返しましたが、サンプルコードをコンパイルするときのコンパイルオプションに問題があるのだろうとあたりをつけて、SDKのサンプルをコンパイルしているときのオプションをログを出力して細かく見ました。差分をArduinoIDEのplatform.txtに設定してみたら、これがビンゴ!無事に動くようになりました。


    かなり癖のあるTensorflow lite/micro ですがTensorflowの最新フレームワークが使えるのは大きな魅力です。これから少しずつ使いこなしていきたいと思います。
    (^^)/






    深層学習&深層強化学習による電子工作 TensorFlow編 (たのしくできる)

    深層学習&深層強化学習による電子工作 TensorFlow編 (たのしくできる)

    • 出版社/メーカー: 東京電機大学出版局
    • 発売日: 2021/06/21
    • メディア: 単行本(ソフトカバー)



    SONY SPRESENSE メインボード CXD5602PWBMAIN1

    SONY SPRESENSE メインボード CXD5602PWBMAIN1

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



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

    SPRESENSEでジェスチャー使って音楽を操作する「MusicGripper」の裏話 [SPRESENSE]

    SPRESENSE の Qiita Advent Calendar のトリとなる記事をあげることができました。SPRESENSEと六軸センサーで傾きを検出して、音楽のボリュームと再生速度を操作するその名も「MusicGripper」です。




    実は、もともとはSPRESENSEを使ったスマートグラスのようなものを作りたいと思っていましたが、0.96インチの液晶ディスプレイが、SPRESENSEで何をやっても動かない。簡単に動くと思ったのですがこれは大誤算。






    レンズや鏡も準備していたのですが、これは困った。動かない原因はおいおい調べるとして、Advent Calendar のトリとしては立派なコンテンツをあげたいところ。このままでは間に合いません。

    いろいろ考えた結果、前からアイディアとしてあたためていたジェスチャーで音楽を操作することを試してみることにしました。

    しかし、順調にものごとが進むことは稀で、ここでもいくつかの壁がありました。一つ目はBLEと音楽再生の平行処理です。BLEの接続は前から調べていたのでなんとかなりますが、今回は音楽再生とBLE受信の平行処理をしなければなりません。

    いろいろ検討したのですが、さすがPythonです。ここは ”Threading” というテクニックを使って解決しました。Pythonは正直苦手なんですが、これは直感的で使いやすいですね。


    import time
    import threading
    
    n = 0
    def func1():
       global n
       while n < 10:
         print ('hello')
         time.sleep(0.5)
    
    thread1 = threading.Thread(target=func1)
    thread1.start()
    while n < 10:
      n = n+1
      print('n='+str(n))
      time.sleep(1)
    thread1.join()
    



    二番目に苦労したのが、再生速度を変える方法です。これがかなり面倒くさい。再生速度を早くするには、オリジナルの音源のサンプル数を間引きしてプレーヤーに渡す必要があります。一方で、再生速度を遅くするにはサンプルの間を補間する必要があります。

    聞くだけでも面倒な処理ですが、ここもPythonの数値ライブラリが活躍してくれました。これらの話を、今後このブログでしていきたいと思います。




    SONY SPRESENSE メインボード CXD5602PWBMAIN1

    SONY SPRESENSE メインボード CXD5602PWBMAIN1

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



    Prament BMI160 モジュール 6DOF 6 軸角速度ジャイロ + 重力加速度センサ IICSPI

    Prament BMI160 モジュール 6DOF 6 軸角速度ジャイロ + 重力加速度センサ IICSPI

    • 出版社/メーカー: Prament
    • メディア: エレクトロニクス



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

    SPRESENSE/Arduino でAVI動画を記録 [SPRESENSE]

    Qiita投稿2回目です。クリスマスまでいくつ記事がかけるかな??今回は、SPRESENSEやArduinoでAVI動画を記録するライブラリを作ってみました。ここで紹介するサンプルはSPRESENSE用ですが、ライブラリそのものはJPEG画像さえ入手できればArduinoでも使えるように設計しています。


    s_github.png YoshinoTaro/AviLibrary_Arduino

    Contribute to YoshinoTaro/AviLibrary_Arduino development by creating an account on GitHub. github.com





    SPRESENSEやArduinoなどの省電力のボードマイコンはカメラ用のISPが内蔵されていないので動画を撮影することはできません。でも、SPRESENSEのカメラのようにカメラモジュールがJPEG出力している場合は、Motion JPEG(AVI)で動画を簡単に記録できます。


    void setup() {
      Serial.begin(115200);
      theCamera.begin();
      while (!theSD.begin()) { Serial.println("insert SD card"); }
    
      // Setup the still picture parameters of Spresense Camera
      // The image format must be JPEG
      theCamera.setStillPictureImageFormat(
         CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V, CAM_IMAGE_PIX_FMT_JPG);
    
      theSD.remove(filename);
      aviFile = theSD.open(filename, FILE_WRITE);
    
      // Initialize AVI library
      theAvi.begin(aviFile, CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V);
      theAvi.startRecording();
    }
    
    void loop() {
      static uint32_t start_time = millis();
      CamImage img = theCamera.takePicture();
    
      // Add a frame. The image format must be JPEG
      theAvi.addFrame(img.getImgBuff(), img.getImgSize());
    
      uint32_t duration = millis() - start_time;
      if (duration > recording_time_in_ms) {
        // Stop AVI library
        theAvi.endRecording();
        theAvi.end();
        theCamera.end();
        while (true) {}
      }
    }
    



    このライブラリは実時間で記録することもできますが、FPSを指定して再生速度を指定することができます。特にタイムラプスの使用を想定しているので、数分毎に撮影した画像を指定したFPSで再生することができます。

    void setup() {
      Serial.begin(115200);
      theCamera.begin();
      while (!theSD.begin()) { Serial.println("insert SD card"); }
    
      // Setup the still picture parameters of Spresense Camera
      // The image format must be JPEG
      theCamera.setStillPictureImageFormat(
         CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V, CAM_IMAGE_PIX_FMT_JPG);
    
      theSD.remove(filename);
      aviFile = theSD.open(filename, FILE_WRITE);
    
      // Initialize AVI library
      theAvi.begin(aviFile, CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V);
      // Start the timelapse specifying fps
      theAvi.startTimelapse(target_fps);
    }
    
    void loop() {
      static uint32_t start_time = millis();
      CamImage img = theCamera.takePicture();
      // Add a timelapse frame. The image format must be JPEG
      theAvi.addTimelapseFrame(img.getImgBuff(), img.getImgSize());
    
      uint32_t duration = millis() - start_time;
      if (duration > recording_time_in_ms) {
        // Stop AVI library
        theAvi.endTimelapse();
        theAvi.end();
        theCamera.end();
        while (true) {}
      }
    }
    


    特にSPRESENSEの場合は、低消費電力で動かすことができるので、数分おきに起動して撮影を繰り返すことでバッテリーでも長時間撮影が可能です。省電力カメラのサンプルも用意しています。夜空の撮影や、旅程の記録、自宅の監視用にぜひぞうぞ。

    void setup() {
      // Initialize LowPower Library
      LowPower.begin();
      RTC.begin();
    
      // get boot cause to check whether this boot is caused by the alarm
      bootcause_e bc = LowPower.bootCause();
    
      Serial.begin(115200);
      theCamera.begin();
      while (!theSD.begin()) { Serial.println("insert SD card"); }
    
      // Setup the still picture parameters of Spresense Camera
      // The image format must be JPEG
      theCamera.setStillPictureImageFormat(
         CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V, CAM_IMAGE_PIX_FMT_JPG);
    
      // In the case of power on / reset, the parameters are set to the initial value.
      if (bc != DEEP_RTC && bc != DEEP_OTHERS) {
        theSD.remove(filename);
        rec_frame = 0;
        movi_size = 0;
        file_size = 0; 
      } else {
        EEPROM.get(rec_frame_address, rec_frame);  // memory the recorded frame count
        EEPROM.get(movi_size_address, movi_size);  // memory the size of the video
        EEPROM.get(file_size_address, file_size);  // memory the file size
      }
    
      aviFile = theSD.open(filename, FILE_WRITE);
    
      // Initialize AVI library
      theAvi.begin(aviFile, CAM_IMGSIZE_QUADVGA_H, CAM_IMGSIZE_QUADVGA_V);
      // set parameters got from EEPROM
      theAvi.setTotalFrame(rec_frame);
      theAvi.setFileSize(file_size);
      theAvi.setMovieSize(movi_size); 
      // Start the timelapse specifying fps
      theAvi.startTimelapse(target_fps);
    }
    
    void loop() {
      CamImage img = theCamera.takePicture();
      // Add a timelapse frame. The image format must be JPEG
      theAvi.addTimelapseFrame(img.getImgBuff(), img.getImgSize());
      // end the recording immediately
      theAvi.endTimelapse();
      theAvi.end();
      theCamera.end();
    
      // memory the updated parameters
      EEPROM.put(rec_frame_address, theAvi.getTotalFrame());
      EEPROM.put(movi_size_address, theAvi.getMovieSize());
      EEPROM.put(file_size_address, theAvi.getFileSize());
    
      // finish the task when the total frame is over the specified number
      if (theAvi.getTotalFrame() > recording_total_frames) {
        while (true) {}
      }
      // power off until  the time specified by sleep_time
      LowPower.deepSleep(sleep_time);
    }
    




    SONY SPRESENSE メインボード CXD5602PWBMAIN1

    SONY SPRESENSE メインボード CXD5602PWBMAIN1

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



    SONY SPRESENSE カメラモジュール CXD5602PWBCAM1

    SONY SPRESENSE カメラモジュール CXD5602PWBCAM1

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



    SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

    SONY SPRESENSE 拡張ボード CXD5602PWBEXT1

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



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

    この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。