前編:Arduinoと赤色レーザーで作るレーザーストリングス
今回は、Arduinoを使ったユニークな電子工作を前編、後編の2回に分けてご紹介しています。
この興味深い電子工作を紹介してくれるのは、物事の関係性をテーマに活動するアーティストである平原真さん。
大阪芸術大学准教授でもある平原さんは、これまでもコンピュータや電子デバイスを使ったメディアアートを多数制作されており、Device Plusでも「Arduinoを使ったソーラーパネルで動くデジタル飼育箱」や「ArduinoとTOF距離センサでつくるドーナツプレーヤ」「Arduinoと加速度センサで作るデジタルボール転がし迷路」といったセンス溢れる電子工作を紹介してくれています。それでは早速見ていきましょう!
目次
- 筐体の作成
- 3.1. 筐体の設計
- 3.2. 部品の制作(レーザー加工機)
- 3.3. 部品の制作(3Dプリンタ)
- 3.4. シナベニヤの接着
- 3.5. 前面パネルに部品を取り付ける
- 3.6. センサを取り付ける
- 3.7. レーザーを取り付ける
- 3.8. ハンドルの取り付け
- 3.9. 上面と背面パネルの取付け
- プログラムの作成
- 4.1. 音響合成ライブラリMozzi
- 4.2. フォトダイオードの状態を読み取り、変化したら音を鳴らす
- 4.3. ボリュームの値を読み取り、音を変化させる
- 4.4. 音名ファイルの読み込み
- 4.5. スケッチ全体
みなさん、こんにちは。平原と申します。今回は、レーザーの弦で演奏できる電子楽器制作をご紹介しています。
前編では、作品全体のプランニングと制作のための準備、電子回路の組み立てをおこないました。後編は、レーザー加工機や3Dプリンタで筐体を作り、Mozziを使ったプログラムを書いて作品を完成させます。今回もよろしくお願いします!
なお、今回の作品のアイデアは、大阪芸術大学の猪熊祐斗さんにご協力いただきました。
3. 筐体の作成
3.1. 筐体の設計
前編の「1-3 筐体のデザイン」では、手描きのスケッチで大体のデザインと構造を考えました。それを元にデータを作成します。複雑な形状であれば、3DCADソフトを使用したほうが正しく設計を行うことができますが、今回は基本的にシンプルな箱型なので、Adobe Illustratorで作図をおこないました。配布ファイルの CutData.aiを確認してください。赤い線はレーザー加工機で切断する部品、緑の線は3Dプリンタで作成する部品です。板をはめ込む箇所は、板厚に注意してください。今回は4mmで設定しています。
3.2. 部品の制作(レーザー加工機)
レーザー加工機で材料を切り出します。筐体の素材は厚み4mmのシナベニヤです。厚みが一致していれば他の素材でも構いません。配布ファイル(1.2MB)の CutData.ai を使ってレーザー加工機で切断してください。前面パネルと背面パネルの大きさが 260mm x 360mm 程度なので、それを加工できる大きさのレーザー加工機を使用してください。
シナベニヤをレーザー加工機で切断すると、表面に汚れがついてしまいます。レーザーの出力とスピードを、切断できるちょうどいい強さに調整することでかなり軽減できます。また、少し手間ですが、マスキングテープで表面を覆うとほとんど汚れなくなります。組立作業中の汚れを防ぐ意味でも、作業が完成してから最後に剥がすのがおすすめです。接着する箇所や部品を取り付ける箇所は適宜取り除いてください。
3.3. 部品の制作(3Dプリンタ)
3Dプリンタで4種類の部品を作ります。
- レーザーの固定具(LaserMounter.stl) x 12
- センサの台座(SensorBase.stl) x 1
- 背面パネル上部の固定具(RearTopFixture.stl) x 2
- 背面パネル下部の固定具(RearBottomFixture.stl) x 2
内部の部品なので積層痕は気にしなくて大丈夫です。フィラメントの色や樹脂の種類も特に指定はありません。お使いの機材で寸法が正しく出力される設定をおこなってください。
3.4. シナベニヤの接着
切断したシナベニヤを接着して筐体を作ります。接着する箇所に木工用接着剤を塗り、張り合わせてからマスキングテープなどで固定してください。はみ出した接着剤は乾燥する前に濡れた布で拭き取ってください。
先に前面パネルに内側の壁になる部品を取り付け、次に側面と底面を接着すると作業しやすいです。上面と背面パネルは、電子部品を取り付けた後でネジ止めで固定するので、接着はしないでください。下の写真では、マスキングテープで仮止めしています。
3.5. 前面パネルに部品を取り付ける
前面パネルに部品を取り付けます。スピーカはスピーカーホルダをかぶせ、外側からM3ネジを通してナットで固定します。ボリュームは一旦ナットを外し、内側から穴に通したあとで外側からナットを取り付けて固定します。センサ台座、背面固定具は外側からM3ネジを通し、ネジ山を切りながら回してそのまま固定してください。穴が緩かったらナットで固定してください。
3.6. センサを取り付ける
センサのブレッドボードを、台座に固定します。上から覗いて、レーザーの穴の真下にセンサが位置するように微調整し、ブレッドボードの裏側の両面テープで固定してください。いきなり全面を貼り付けると修正しづらいので、剥離紙の一部だけ剥がし仮止めするといいでしょう。
3.7. レーザーを取り付ける
レーザーを固定具に取り付けます。レーザーの先端にはレンズが入っていて、ネジを回すと焦点距離を調整することができますが、そのまま回して外してください。焦点距離を固定するためにM5 厚み0.8mmのナットを2枚挟み、レンズを元に戻してください。レンズが外に出てしまった場合は裏表に気をつけて元に戻してください。3Dプリンタで作ったレーザーの固定具に差し込んでください。
レーザー固定具を筐体の内側の上面に取り付けます。下側からM3ネジを通してください。このあと位置合わせをするので、ゆるく取り付けてください。ケーブルはマジックテープのベルトなどで束ねておくと、絡まらず扱いやすいです。
レーザーの位置合わせをします。USBケーブルをPCにつなぎ電源を入れてください。レーザーが照射されますが、ほとんどはセンサに当たっていないと思います。ネジを緩めて左右に動かしたり、ホルダと筐体の間にM3ワッシャを挟み前後に動かして、全てのレーザーがセンサに当たるように調整してください。シリアルモニタでセンサが反応していることを確認できます。かなり繊細な調整が必要なので根気よく作業をおこなってください。
すべての配線を終え、レーザーの位置合わせをした状態です。配線の本数が多くて大変ですが、頑張ってください。
3.8. ハンドルの取り付け
上面の板にハンドルを取り付けます。金属部品とバンドにM4皿ネジを通し、裏側からナットで固定してください。
3.9. 上面と背面パネルの取付け
最後に、上面と背面パネルを固定します。3Dプリンタで作った固定具には直径3mmの穴が空いているので、M3ネジでを切り込みながら固定できます。
これでハードウェアは完成です。お疲れさまでした!
次はMozziを使って音を鳴らすプログラムを作っていきましょう。
4. プログラムの作成
それではArduinoのプログラムについて見ていきましょう。今回の作品に必要な主な機能は以下の4つです。
- 音響合成ライブラリMozzi
- フォトダイオードの状態を読み取り、変化したら音を鳴らす
- ボリュームの値を読み取り、音を変化させる
- 音名ファイルの読み込み
それぞれの機能について説明したあとで、全体のプログラムを掲載します。
4.1. 音響合成ライブラリMozzi
Arduinoで音を鳴らす方法はいくつかあります。Arduino標準のtone()関数を使うと周波数を指定して音を鳴らすことができますが、シンプルなビープ音なので表現力は物足りません。また、SDカードに記録した音源データを再生する専用の部品を使うと、リッチな音源を鳴らすことができますが、リアルタイムに変化させることはできません。そこで、今回はリアルタイムにリッチな音を生成することができる音響合成ライブラリ「Mozzi」を使用します。
>> Mozzi公式サイト
Mozziの使い方について、デバイスプラスのこちらの記事も参照してください。
>> Arduinoでパーツやセンサを使ってみよう~スピーカ編(その2)
Mozziのインストール
公式サイトの「download」から、「Download from GitHub」に進み、GitHubからZipファイルをダウロードしてください。Arduino IDE の [スケッチ] > [ライブラリをインクルード] > [.ZIP形式のライブラリをインストール…]を選択し、ダウンロードしたファイルを読み込んでください。
オシレータで音を鳴らす
オシレータを使って音の元になる波形を生成します。ライブラリの読み込みから、オブジェクトの生成、出力する音の取り出し方を見ていきましょう。配布ファイルのLaserStrings.inoを開き、全体を確認しながら説明を読んでください。
#include <MozziGuts.h>//Mozziライブラリの読み込み #include <Oscil.h>//オシレーターの読み込み #include <tables/square_no_alias_2048_int8.h>//矩形波 #include <tables/saw2048_int8.h>//ノコギリ波 #include <tables/triangle_valve_2048_int8.h>//三角波 #include <tables/sin2048_int8.h>//サイン波 ...省略... Oscil <2048, AUDIO_RATE> oscArray[STRING_NUM];//オシレーターの配列
はじめにMozziの本体と、オシレータ、波形のテーブルデータを読み込みます。次にオシレータオブジェクトを生成します。今回は12個のオシレータを配列として扱います。
void setup() { startMozzi(CONTROL_RATE);//Mozziのスタート ...省略... }
setup関数の中でMozziの処理を開始します。
void updateControl() { ...省略... }
Mozziではloop関数の中にオーディオの処理しか書くことができません。代わりにupdateControl()関数に処理を書きます。
int updateAudio() { int out = 0; for ( int i = 0; i < STRING_NUM; i++) { out += (oscArray[i].next() * gain[i]) >> 8;//オシレーターの出力を合算 } out /= STRING_NUM;//平均 return out; }
updateAudio()関数では、出力する音をreturnで返します。オシレータのnext()関数を呼び出し処理を進め、12個のオシレータの出力を合計し、平均した値を求めています。
void loop() { audioHook();//オーディオ再生。他の要素を書いてはいけない。 }
Mozziを使う場合、loop関数の中にはaudioHook() しか書くことができません。
4.2. フォトダイオードの状態を読み取り、変化したら音を鳴らす
レーザーを遮るとセンサは暗い状態になりますが、遮っている時に常に音を鳴らすと、機械的で不自然な感じがします。遮った瞬間だけ音量を上げ、徐々に減衰させることで、指で弾いたような自然な感じになります。
int prevState[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};//1つ前のセンサーの状態
はじめに、それぞれのセンサの以前の状態を保持しておく配列を作ります
for ( int i = 0; i < STRING_NUM; i++) { int crntState = digitalRead(pin[i]);//フォトダイオードの値を読み取る if ( crntState == false && prevState[i] == true )//フォトダイオードの立ち下がり { gain[i] = volume;//音量を設定 } gain[i] *= decay / 100; //減衰する prevState[i] = crntState; }
updateControl()の中で、digitalRead()を使いセンサの状態を読み取ります。現在の値がfalse(暗い)で、前に調べた時の値がtrue(明るい)場合、レーザーが遮られた瞬間であることが分かります。gainにvolumeを代入して音量を上げます。最後にcrntStateをprevStateに代入し更新します。
4.3. ボリュームの値を読み取り、音を変化させる
4つのボリュームにはそれぞれ音に変化をつける機能を割り当てています。
- ボリューム1:音量
- ボリューム2:音程
- ボリューム3:響き
- ボリューム4:波形の種類
音量と響き
int volume = map(mozziAnalogRead(A0), 0, 1023, 0, 255);//VR1を読み取り、音量を変える float decay = map(mozziAnalogRead(A2), 0, 1023, 70, 100 );//VR3を読み取り、減衰率を変える for ( int i = 0; i < STRING_NUM; i++) { int crntState = digitalRead(pin[i]);//フォトダイオードの値を読み取る if ( crntState == false && prevState[i] == true )//フォトダイオードの立ち下がり { gain[i] = volume;//音量を設定 } gain[i] *= decay / 100; //減衰する prevState[i] = crntState; }
mozziAnalogRead()でボリューム1の値を読み取り、フォトダイオードの反応後に音量(gain)として設定しています。ボリューム3の値を読み取り、減衰率(decay)として設定しています。値が大きいと長く響き、小さいと短く消えます。
音程と波形の種類
int rate = map(mozziAnalogRead(A1), 0, 1024, 1, 5);//VR2を読み取り、音程を変える setFreqs( rate );//周波数の設定 int waveType = map(mozziAnalogRead(A3), 0, 1024, 0, 4);//VR4を読み取り、波形を変える setWaveType( waveType );//波形の設定
ボリュームの値を読み取りオシレータに周波数と波形の設定を行います。MozziではArduinoの機能をギリギリまで使っているため、通常のanalogRead()関数を使用できません。代わりにmozziAnalogRead()関数を使います。読み取った値を音程は1~4、波形は0~3の整数に整えています。
void setFreqs( int rate )//周波数の設定 { for ( int i = 0; i < STRING_NUM; i++) { oscArray[i].setFreq(baseFreq[i] * rate); } }
12個のオシレータの周波数を設定します。基準となる周波数(baseFreq[])にrateを掛けて、4オクターブを切り替える事ができます。
void setWaveType( int waveType )//波形の設定 { int waveData; switch ( waveType ) { case 0://矩形波 waveData = SQUARE_NO_ALIAS_2048_DATA; break; case 1://ノコギリ波 waveData = SAW2048_DATA; break; case 2://三角波 waveData = TRIANGLE_VALVE_2048_DATA; break; default://サイン波 waveData = SIN2048_DATA; break; } for ( int i = 0; i < STRING_NUM; i++) { oscArray[i].setTable(waveData);//波形の設定 } }
12個のオシレータの波形の種類を切り替えます。矩形波はいわゆるビープ音で硬い音、ノコギリ波、三角波、サイン波の順に滑らかな波形で、柔らかい音になります。同じ音量設定でも矩形波は大きく、サイン波は小さく聞こえます。
4.4. 音名ファイルの読み込み
オシレータには周波数を設定することができますが、数字ではわかりにくいので、C5など音名で置き換えられるようにします。配布ファイルの LaserStrings/pitches.h を開いてみてください。「NOTE_B0」などが音名、「31」などが周波数です。
#define NOTE_B0 31 #define NOTE_C1 33 #define NOTE_CS1 35 #define NOTE_D1 37 #define NOTE_DS1 39 #define NOTE_E1 41 #define NOTE_F1 44 #define NOTE_FS1 46 #define NOTE_G1 49
このファイルをLaserStrings.inoと同じフォルダに入れた状態でファイルを読み込みます。
#include "pitches.h"
周波数ではなく音名で表記することができるようになります。
int baseFreq[] = {NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, NOTE_E3, NOTE_F3, NOTE_FS3, NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, NOTE_B3};//各弦の基準の周波数
4.5. スケッチ全体
下記のソースコードをArduino UNOに書き込んでください。配布ファイルの LaserStrings.ino です。
#include <MozziGuts.h>//Mozziライブラリの読み込み #include <Oscil.h>//オシレーターの読み込み #include <tables/square_no_alias_2048_int8.h>//矩形波 #include <tables/saw2048_int8.h>//ノコギリ波 #include <tables/triangle_valve_2048_int8.h>//三角波 #include <tables/sin2048_int8.h>//サイン波 #include "pitches.h" #define CONTROL_RATE 128//更新頻度。 高い周波数が必要な場合は高くする。2の累乗 #define STRING_NUM 12//弦の本数 Oscil <2048, AUDIO_RATE> oscArray[STRING_NUM];//オシレーターの配列 byte gain[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};//音量 int pin[] = {1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13};//フォトダイオードに繋がるピン番号 int prevState[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};//1つ前のセンサーの状態 int baseFreq[] = {NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5};//各弦の基準の周波数 #define DEBUG_MODE false//センサーやボリュームの値をシリアルモニターで確認したい時はtrueに変える void setup() { startMozzi(CONTROL_RATE);//mozziのスタート for ( int i = 0; i < STRING_NUM; i++) { pinMode(pin[i], INPUT);//DigitalReadで使うピンモードの設定 } digitalWrite( A5, LOW );//アンプをON if (DEBUG_MODE) { Serial.begin(9600);//デバッグ用 } } void updateControl() { // デバッグ用モニタリング ------------------ if ( DEBUG_MODE ) { Serial.print( "PD : " ); for ( int i = 0; i < STRING_NUM; i++) { int pd = digitalRead(pin[i]); Serial.print( pd ); Serial.print( " / " ); } Serial.print( " VR : " ); Serial.print( mozziAnalogRead(A0) ); Serial.print( " / " ); Serial.print( mozziAnalogRead(A1) ); Serial.print( " / " ); Serial.print( mozziAnalogRead(A2) ); Serial.print( " / " ); Serial.print( mozziAnalogRead(A3) ); Serial.println(); } int volume = map(mozziAnalogRead(A0), 0, 1023, 0, 255);//VR1を読み取り、音量を変える float decay = map(mozziAnalogRead(A2), 0, 1023, 70, 100 );//VR3を読み取り、減衰率を変える for ( int i = 0; i < STRING_NUM; i++) { int crntState = digitalRead(pin[i]);//フォトダイオードの値を読み取る if ( crntState == false && prevState[i] == true )//フォトダイオードの立ち下がり { gain[i] = volume;//音量を設定 } gain[i] *= decay / 100; //減衰する prevState[i] = crntState; } int rate = map(mozziAnalogRead(A1), 0, 1024, 1, 5);//VR2を読み取り、音程を変える setFreqs( rate );//周波数の設定 int waveType = map(mozziAnalogRead(A3), 0, 1024, 0, 5);//VR4を読み取り、波形を変える setWaveType( waveType );//波形の設定 } int updateAudio() { int out = 0; for ( int i = 0; i < STRING_NUM; i++) { out += (oscArray[i].next() * gain[i]) >> 8;//オシレーターの出力を合算 } out /= STRING_NUM;//平均 return out; } void loop() { audioHook();//オーディオ再生。他の要素を書いてはいけない。 } void setFreqs( int rate )//周波数の設定 { for ( int i = 0; i < STRING_NUM; i++) { oscArray[i].setFreq(baseFreq[i] * rate); } } void setWaveType( int waveType )//波形の設定 { int waveData; switch ( waveType ) { case 0://矩形波 waveData = SQUARE_NO_ALIAS_2048_DATA; break; case 1://ノコギリ波 waveData = SAW2048_DATA; break; case 2://三角波 waveData = TRIANGLE_VALVE_2048_DATA; break; default://サイン波 waveData = SIN2048_DATA; break; } for ( int i = 0; i < STRING_NUM; i++) { oscArray[i].setTable(waveData);//波形の設定 } }
おわりに
Arduinoにプログラムを書き込むと演奏できる状態になります。無事に音が鳴ったでしょうか?正面から見て右が低音、左が高音です。ドから1オクターブの音が出ます。ボリュームは左から、音量、音程、響き、波形の種類を調整できます。レーザーを指で遮りながら、ボリュームを動かして、色々な音色を楽しんでください。
今回はレーザーを縦に配置しハープのような形状にしましたが、筐体のデザインを変えて、ギター型やピアノ型など色々なバリエーションを作ることができそうです。また、Mozziにはまだまだ使っていない機能があります。ぜひ皆さんも独自に、もっと豊かな表現をできるようにプログラムを改良してみてください。
今回の連載の流れ
前編:Arduinoと赤色レーザーで作るレーザーストリングス
後編:Arduinoと赤色レーザーで作るレーザーストリングス(今回)