K_Yokoyama が 2024年01月29日17時48分51秒 に編集
初版
タイトルの変更
Spresenseと距離センサを使ったペンダント型楽器(仮)
タグの変更
spresense
SPRESENSE
楽器
超音波距離センサ
ゆる楽器
記事種類の変更
製作品
本文の変更
# はじめに 皆さんは「**ゆる楽器**」という概念をご存知ですか? この言葉は「ゆるい」と「楽器」を組み合わせた言葉で、誰でも簡単に演奏できる気軽で楽しい楽器のことです。 特別な技術や練習がなくても、リズム感や音感がなくても`誰でもすぐに簡単に音楽を楽しむことができる`のがゆる楽器の特徴です。 今回はSpresenseと超音波距離センサを使ってゆる楽器の制作に取り組みました。 # コンセプト 今回の制作にあたってテルミンという楽器を参考にしました。これは両手を空間にかざして動かすだけで音を制御することができる電子楽器です。 >https://ja.wikipedia.org/wiki/%E3%83%86%E3%83%AB%E3%83%9F%E3%83%B3 この楽器の手を空間で動かすだけで不思議な音が鳴るという点が面白いと感じました。これをもっと簡単に場所を取らずに体験できたらと思い、`手をかざすだけで音が鳴る`という要素と`場所を取らない`という要素を中心に、次のようなコンセプトを考えました。 **〜首からかけて手を体の前で動かすだけのテルミン風ペンダント型楽器〜** **「メロディペンダント」** 距離センサをペンダントにして首からかけることで、手を体の前で動かすだけで音が鳴る、そんな楽器を目指しました。 # 使用したもの |名称|概要| |---|---|---| |Spresenseメインボード|メイン処理を行うマイコン。Arduino 互換あり。| |Spresense拡張ボード|Spresenseメインボード用のアドオン。機能や接続を追加できる。| |Mic&LCD KIT for SPRESENSE|LCDのみ使用。| |超音波距離センサ hc-sr04|超音波の反射時間を利用して非接触で距離を測定することができる。| |可変抵抗ボリュームスイッチ|音のテンポ変更に使用する。| |3Dプリンタ|スピーカーのケースと本体部分の製作| |USBミニスピーカー|USB電源でイヤホンジャックで接続。| # 仕組み 本作品の基本的な仕組みは以下の通りです。 ![基本の仕組み](https://camo.elchika.com/904be553a8a69e32e8a5f41bc4df48ca1382356d/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39393662663537612d326334622d346432332d613334352d3862616362636532643932612f34633839616438372d343131362d343233642d623534372d356138313062373139373466/) 首からぶら下げた超音波センサが前方向の距離を計測し、その前で手を前後させることで距離に対応した音が鳴ります。 ![音と距離の対応関係](https://camo.elchika.com/6f7eef85c5a3ee01b41e4645e6b221da9488abb9/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39393662663537612d326334622d346432332d613334352d3862616362636532643932612f31376530613262362d646430372d343436652d383434342d623865666435346234383664/) 音は立ち上がってからフェードアウトまでが、一定の秒数で繰り返し鳴ります。つまみを回すことでその速さを調節することができます。 ![音の鳴り方](https://camo.elchika.com/11d14ebab9d40a3834e5d36e705f044190b581b5/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39393662663537612d326334622d346432332d613334352d3862616362636532643932612f62623363653939632d323766372d343839642d616637392d666466373430613038383733/) それぞれの音に対応して棒人間のアニメーションがLCDに表示され、まるで棒人間が踊っているように見えます。 # 制作 センサを格納するペンダント部分と、Spresenseを格納する本体部分を3Dプリンタで制作しました。 (未) プログラムコードは記事の最後にあります。 # デモ動画 (未) ロボットの頭部に見立てたセンサを首からぶら下げ、その前で手を前後させることで距離に対応した音が鳴ります。 音は立ち上がってからフェードアウトまでが、一定の秒数で繰り返し鳴ります。つまみを回すことでその速さを調節することができます。 それぞれの音に対応して棒人間のアニメーションがLCDに表示され、まるで棒人間が踊っているように見えます。 # パートナーさんからのフィードバック 今回の制作には実際にプロトタイプを見ていただき、意見をいただいたパートナーの方達がいらっしゃいます。進捗共有の際に以下のような意見をいただきました。 **【肯定的な意見】** - 首からぶら下げるというアイデアがいい。 - センサのケースがロボットのように見えて可愛い。 **【アドバイス】** - ペンダントにある程度の重さがあると嫌悪感につながる可能性がある。 - 音だけでなく光など、別のフィードバックがあった方が楽しそう。(この時点ではLCDは未実装だった) 首から下げるというアイデアに共感していただいた一方で、体に密着するものになるので使いやすさや重さへの不安を感じていたという印象があります。 これらの意見を参考にしながら制作を進めています。 # 工夫した点 センサをペンダントとして独立させたことで、実際に演奏をするときはペンダントを首からかけて手を体の前で前後させるだけで音を鳴らせます。複雑な手の動きが全く必要なく、感覚で演奏ができます。 また、パートナーさんの意見をもとに、LCDを用いた映像での出力を追加したことで自分の動きに対して音と映像でフィードバックを得ることができます。 # 今後の課題 センサ部分はSpresenseと有線で繋がっているので、本当の意味でどこでも使えるようにするにはセンサの無線化が望ましいです。ですがこれは同時にペンダントの重量が増える可能性が大きいのでセンサパーツの規格から考え直す必要があると考えています。 また映像によるフィードバックについては非常に簡単な棒人間表示にとどまっているので、「ペンダント部分がロボットに見えてかわいい」というパートナーさんの意見を参考に、ロボットが表示されるような映像を目指したいです。 # プログラムコード イヤホンジャックへビープ音を出力するためのAudioライブラリに加え、LCDの描画を行うためのライブラリも使用しています。 ```arduino:Spresense用ソースコード #include <Audio.h> #include "SPI.h" #include "Adafruit_GFX.h" #include "Adafruit_ILI9341.h" #define trigPin 2 #define echoPin 3 #define TFT_DC 9 #define TFT_CS 10 Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC); AudioClass *theAudio; int ptone = 1; int set = 1; int pset = 1; void playToneWithFade(int tone, double fadeOutTime) { double volume = -70; //初期音量 double stepTime = fadeOutTime / 100; //音量を下げるステップの時間 theAudio->setBeep(1, volume, tone); for (int i = 0; i < 10; i++) { //音を立ち上げる volume += 6; theAudio->setBeep(1, volume, tone); delay(stepTime); } for (int i = 0; i < 10; i++) { //最大音量から少し下げる volume -= 2; theAudio->setBeep(1, volume, tone); delay(stepTime); } for (int i = 0; i < 80; i++) { //だんだんフェードアウトする volume -= 0.5; theAudio->setBeep(1, volume, tone); delay(stepTime); } theAudio->setBeep(0, volume, tone); //音を止める } void setup() { Serial.begin (9600); pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); theAudio = AudioClass::getInstance(); theAudio->begin(); puts("initialization Audio Library"); theAudio->setPlayerMode(AS_SETPLAYER_OUTPUTDEVICE_SPHP, 0, 0); tft.begin(); } void loop() { long duration, distance; int tone = 0; int flag = 0; //音を変更するかどうかの確認のフラグ digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); duration = pulseIn(echoPin, HIGH); //距離を計測 distance = duration / 58; //cmに変換 int val; val = analogRead(0); //可変抵抗の値をとる if(val < 100) { //テンポ小さすぎると音が出ないので100以上に調節 val = 100; } double time = val * 2; //秒に変換(概算) int centerX = tft.width() / 2; int centerY = tft.height() / 2; int radius = min(tft.width(), tft.height()) / 5; // 頭の半径 int armparts = radius / 1.5; //腕の長さの基準 int legparts = radius / 1.3; //足の長さの基準 tft.drawCircle(centerX, centerY + radius * 2, radius, ILI9341_WHITE); //頭を描画 tft.drawLine(centerX, centerY + radius, centerX, centerY - radius, ILI9341_WHITE); //体を描画 if (distance <= 8 && ptone != tone) { //ドの音を鳴らす場合の処理 tft.drawLine(centerX, centerY + armparts / 2, centerX - armparts * 2, centerY + armparts * 2, ILI9341_WHITE); //右腕 tft.drawLine(centerX, centerY + armparts / 2, centerX + armparts * 2, centerY + armparts * 2, ILI9341_WHITE); //左腕 tft.drawLine(centerX, centerY - radius, centerX - legparts * 2, centerY - radius - legparts * 2, ILI9341_WHITE); //右足 tft.drawLine(centerX, centerY - radius, centerX + legparts * 2, centerY - radius - legparts * 2, ILI9341_WHITE); //左足 tone = 262;//Do flag = 1; } else if(distance <= 12 && ptone != tone) { //レの音を鳴らす場合の処理 tft.drawLine(centerX, centerY + armparts / 2, centerX + radius, (centerY + armparts / 2) + radius, ILI9341_WHITE); //右腕1 tft.drawLine(centerX + radius, (centerY + armparts / 2) + radius, centerX + radius + 20, (centerY + armparts / 2) + radius * 2, ILI9341_WHITE); //右腕2 tft.drawLine(centerX, centerY + armparts / 2, centerX - radius, (centerY + armparts / 2) - radius, ILI9341_WHITE); //左腕1 tft.drawLine(centerX - radius, (centerY + armparts / 2) - radius, centerX - radius - 20, (centerY + armparts / 2) - radius * 2, ILI9341_WHITE); //左腕2 tft.drawLine(centerX, centerY - radius, centerX - legparts * 2, centerY - radius - legparts * 2, ILI9341_WHITE); //右足 tft.drawLine(centerX, centerY - radius, centerX + legparts * 2, centerY - radius - legparts * 2, ILI9341_WHITE); //左足 tone = 294;//Re flag = 1; } else if(distance <= 16 && ptone != tone) { //ミの音を鳴らす場合の処理 tft.drawLine(centerX, centerY + armparts / 2, centerX - radius, (centerY + armparts / 2) + radius, ILI9341_WHITE); //右腕1 tft.drawLine(centerX - radius, (centerY + armparts / 2) + radius, centerX - radius - 20, (centerY + armparts / 2) + radius * 2, ILI9341_WHITE); //右腕2 tft.drawLine(centerX, centerY + armparts / 2, centerX + radius, (centerY + armparts / 2) - radius, ILI9341_WHITE); //左腕1 tft.drawLine(centerX + radius, (centerY + armparts / 2) - radius, centerX + radius + 20, (centerY + armparts / 2) - radius * 2, ILI9341_WHITE); //左腕2 tft.drawLine(centerX, centerY - radius, centerX - legparts * 2, centerY - radius - legparts * 2, ILI9341_WHITE); //右足 tft.drawLine(centerX, centerY - radius, centerX + legparts * 2, centerY - radius - legparts * 2, ILI9341_WHITE); //左足 tone = 330;//Mi flag = 1; } else if(distance <= 20 && ptone != tone) { //ファの音を鳴らす場合の処理 tft.drawLine(centerX, centerY + armparts / 2, centerX + radius, (centerY + armparts / 2) + radius, ILI9341_WHITE); //右腕1 tft.drawLine(centerX + radius, (centerY + armparts / 2) + radius, centerX + radius + 20, (centerY + armparts / 2) + radius * 2, ILI9341_WHITE); //右腕2 tft.drawLine(centerX, centerY + armparts / 2, centerX - radius, (centerY + armparts / 2) - radius, ILI9341_WHITE); //左腕1 tft.drawLine(centerX - radius, (centerY + armparts / 2) - radius, centerX - radius - 20, (centerY + armparts / 2) - radius * 2, ILI9341_WHITE); //左腕2 tft.drawLine(centerX, centerY - radius, centerX + radius, centerY + radius - 15, ILI9341_WHITE); //右足1 tft.drawLine(centerX + radius, centerY + radius - 15, centerX + radius, centerY - radius, ILI9341_WHITE); //右足2 tft.drawLine(centerX, centerY - radius, centerX - legparts * 2, centerY - radius - legparts * 2, ILI9341_WHITE); //右足 tone = 349;//Fa flag = 1; } else if(distance <= 24 && ptone != tone) { //ソの音を鳴らす場合の処理 tft.drawLine(centerX, centerY + armparts / 2, centerX - radius, (centerY + armparts / 2) + radius, ILI9341_WHITE); //右腕1 tft.drawLine(centerX - radius, (centerY + armparts / 2) + radius, centerX - radius - 20, (centerY + armparts / 2) + radius * 2, ILI9341_WHITE); //右腕2 tft.drawLine(centerX, centerY + armparts / 2, centerX + radius, (centerY + armparts / 2) - radius, ILI9341_WHITE); //左腕1 tft.drawLine(centerX + radius, (centerY + armparts / 2) - radius, centerX + radius + 20, (centerY + armparts / 2) - radius * 2, ILI9341_WHITE); //左腕2 tft.drawLine(centerX, centerY - radius, centerX - radius, centerY + radius - 15, ILI9341_WHITE); //右足1 tft.drawLine(centerX - radius, centerY + radius - 15, centerX - radius, centerY - radius, ILI9341_WHITE); //右足2 tft.drawLine(centerX, centerY - radius, centerX + legparts * 2, centerY - radius - legparts * 2, ILI9341_WHITE); //左足 tone = 392;//So flag = 1; } else if(distance <= 28 && ptone != tone) { //ラの音を鳴らす場合の処理 tft.drawLine(centerX, centerY + armparts / 2, centerX + radius, (centerY + armparts / 2) + radius, ILI9341_WHITE); //右腕1 tft.drawLine(centerX + radius, (centerY + armparts / 2) + radius, centerX + radius + 20, (centerY + armparts / 2) + radius * 2, ILI9341_WHITE); //右腕2 tft.drawLine(centerX, centerY + armparts / 2, centerX + radius, centerY + armparts / 2, ILI9341_WHITE); //左腕1 tft.drawLine(centerX + radius, centerY + armparts / 2, centerX + radius * 2, centerY + armparts + 15, ILI9341_WHITE); //左腕2 tft.drawLine(centerX, centerY - radius, centerX + radius, centerY - radius - 15, ILI9341_WHITE); //右足1 tft.drawLine(centerX + radius, centerY - radius - 15, centerX + radius - 40, centerY - radius - 50, ILI9341_WHITE); //右足2 tft.drawLine(centerX, centerY - radius, centerX - legparts * 2, centerY - radius - legparts * 2, ILI9341_WHITE); //左足 tone = 440;//Ra flag = 1; } else if(distance <= 32 && ptone != tone) { //シの音を鳴らす場合の処理 tft.drawLine(centerX, centerY + armparts / 2, centerX - radius, (centerY + armparts / 2) + radius, ILI9341_WHITE); //右腕1 tft.drawLine(centerX - radius, (centerY + armparts / 2) + radius, centerX - radius - 20, (centerY + armparts / 2) + radius * 2, ILI9341_WHITE); //右腕2 tft.drawLine(centerX, centerY + armparts / 2, centerX - radius, centerY + armparts / 2, ILI9341_WHITE); //左腕1 tft.drawLine(centerX - radius, centerY + armparts / 2, centerX - radius * 2, centerY + armparts + 15, ILI9341_WHITE); //左腕2 tft.drawLine(centerX, centerY - radius, centerX - radius, centerY - radius - 15, ILI9341_WHITE); //右足1 tft.drawLine(centerX - radius, centerY - radius - 15, centerX - radius + 40, centerY - radius - 50, ILI9341_WHITE); //右足2 tft.drawLine(centerX, centerY - radius, centerX + legparts * 2, centerY - radius - legparts * 2, ILI9341_WHITE); //左足 tone = 494;//Si flag = 1; } else if(distance <= 36 && ptone != tone) { //高い方のドの音を鳴らす場合の処理 tft.drawLine(centerX, centerY + armparts / 2, centerX - armparts * 2, centerY + armparts * 2, ILI9341_WHITE); //右腕1 tft.drawLine(centerX, centerY + armparts / 2, centerX + armparts * 2, centerY + armparts * 2, ILI9341_WHITE); //左腕1 tft.drawLine(centerX, centerY - radius, centerX - legparts * 2.2, centerY - radius - 10, ILI9341_WHITE); //右足 tft.drawLine(centerX, centerY - radius, centerX + legparts * 2.2, centerY - radius - 10, ILI9341_WHITE); //左足 tone = 523;//Do2 flag = 1; } if(distance > 36) { //計測距離の表示と棒立ちポーズの表示 Serial.println("Out of range"); tft.drawLine(centerX, centerY + armparts / 2, centerX - armparts * 2, centerY - armparts * 2, ILI9341_WHITE); //右腕 tft.drawLine(centerX, centerY + armparts / 2, centerX + armparts * 2, centerY - armparts * 2, ILI9341_WHITE); //左腕 tft.drawLine(centerX, centerY - radius, centerX - legparts * 2, centerY - radius - legparts * 2, ILI9341_WHITE); //右足 tft.drawLine(centerX, centerY - radius, centerX + legparts * 2, centerY - radius - legparts * 2, ILI9341_WHITE); //左足 } else { Serial.print(distance); Serial.println(" cm"); } if(flag == 1) { //音の再生と画面のリセット playToneWithFade(tone, time); //音を鳴らす ptone = tone; tft.fillScreen(ILI9341_BLACK); //画面を黒でリセット flag == 0; } pset = set; } ```