K_Yokoyama が 2024年01月30日01時13分29秒 に編集
コメント無し
本文の変更
# はじめに
皆さんは「**ゆる楽器**」という概念をご存知ですか?
皆さんは「**ゆる楽器**」という概念をご存知ですか?
この言葉は「ゆるい」と「楽器」を組み合わせた言葉で、誰でも簡単に演奏できる気軽で楽しい楽器のことです。
この言葉は「ゆるい」と「楽器」を組み合わせた言葉で、誰でも簡単に演奏できる気軽で楽しい楽器のことです。
特別な技術や練習がなくても、リズム感や音感がなくても`誰でもすぐに簡単に音楽を楽しむことができる`のがゆる楽器の特徴です。
特別な技術や練習がなくても、リズム感や音感がなくても`誰でもすぐに簡単に音楽を楽しむことができる`のがゆる楽器の特徴です。
今回はSpresenseと超音波距離センサを使ってゆる楽器の制作に取り組みました。 # パートナーさんについて 今回のプロジェクトには現在支援学校に通う2組のご家庭に協力してもらいながら開発を進めています。 どちらのお子さんも小学生で、常にバギーで行動しなければならないRちゃんと、声が出せないAちゃんの二人に協力してもらっています。この二人と親御さんとも意見を交換しながら、誰でも楽しむことができるメディア遊び、すなわち「ゆる楽器」を作ることが本プロジェクトの最終的な目標となっています。 開発の初期段階では実際に対面でリサーチを行い、さまざまな道具を用意してどんな楽器や音に興味があるのか、どのような機能なら使いやすいと思ってもらえるのかを事前に考えた上でアイデアを考えました。開発の途中でもフィードバックをいただき、かなり政策の参考にさせていただいています。 # コンセプト 今回の制作にあたってテルミンという楽器を参考にしました。これは両手を空間にかざして動かすだけで音を制御することができる電子楽器です。 >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/)
音は立ち上がってからフェードアウトまでが、一定の秒数で繰り返し鳴ります。つまみを回すことでその速さを調節することができます。 本来音をシンセサイズする場合はAttack Time(アタックタイム)、Decay Time(ディケイタイム)、Sustain Level(サステインレベル)、Release Time(リリースタイム)のADSRの要素を考えるヒッツ用があります。(以下の画像参照)
![音の要素](https://camo.elchika.com/20ceaefa85d59dede52b839706a320dfcd789dde/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39393662663537612d326334622d346432332d613334352d3862616362636532643932612f39393937316462622d656130322d346364642d626234302d396632656431653136643564/) >https://jp.yamaha.com/products/contents/music_production/guide_to_synth/003/index.html しかし、検証の結果ビープ音が短くなると「プツッ」といったノイズ感のある音が発生しやすく、リリースタイムをある程度確保しないとフェードアウトしているかどうかが聞こえないような音になってしまいました。そこで今回はサステインレベルをなくし、以下のような設定で音を鳴らしています。 ![音の仕組み](https://camo.elchika.com/0d6fef4caf2d00f3d245895c25027fb55786337e/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f39393662663537612d326334622d346432332d613334352d3862616362636532643932612f36373432333762352d633739622d343765642d616537642d353664643735323335333962/)
それぞれの音に対応して棒人間のアニメーションがLCDに表示され、まるで棒人間が踊っているように見えます。 # 制作 センサを格納するペンダント部分と、Spresenseを格納する本体部分を3Dプリンタで制作しました。 (未) 回路図は以下の通りです (未) プログラムコードは記事の最後にあります。 # デモ動画 (未) ロボットの頭部に見立てたセンサを首からぶら下げ、その前で手を前後させることで距離に対応した音が鳴ります。 音は立ち上がってからフェードアウトまでが、一定の秒数で繰り返し鳴ります。つまみを回すことでその速さを調節することができます。 それぞれの音に対応して棒人間のアニメーションがLCDに表示され、まるで棒人間が踊っているように見えます。 # パートナーさんからの意見 12月時点での進捗共有の際にはパートナーさん達から以下のような意見をいただきました。 **【肯定的な意見】** - 首からぶら下げるというアイデアがいい。 - センサのケースがロボットのように見えて可愛い。 **【アドバイス】** - ペンダントにある程度の重さがあると嫌悪感につながる可能性がある。 - 音だけでなく光など、別のフィードバックがあった方が楽しそう。(この時点ではLCDは未実装だった) 首から下げるというアイデアに共感していただいた一方で、体に密着するものになるので使いやすさや重さへの不安を感じていたという印象があります。 これらの意見は実際にかなり参考にさせていただきました。 # 工夫した点 センサをペンダントとして独立させたことで、実際に演奏をするときはペンダントを首からかけて手を体の前で前後させるだけで音を鳴らせます。複雑な手の動きが全く必要なく、感覚で演奏ができます。 また、パートナーさんの意見をもとに、LCDを用いた映像での出力を追加したことで自分の動きに対して音と映像でフィードバックを得ることができます。 # 今後の課題 センサ部分はSpresenseと有線で繋がっているので、本当の意味でどこでも使えるようにするにはセンサの無線化が望ましいです。ですがこれは同時にペンダントの重量が増える可能性が大きいのでセンサパーツの規格から考え直す必要があると考えています。 また映像によるフィードバックについては非常に簡単な棒人間表示にとどまっているので、「ペンダント部分がロボットに見えてかわいい」というパートナーさんの意見を参考に、ロボットが表示されるような映像を目指したいです。
さらに本体部分は現在ダイソーのUSBスピーカーを使用していますが、ペンダントは簡易でも音が鳴る本体に結局ある程度の場所の制限が生まれてしまっているので、もっと気軽に使えるように改良を行いたいです。
# プログラムコード イヤホンジャックへビープ音を出力するための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; } ```