mbed LPC1768と圧電スピーカーで音楽を鳴らす

背景

先日MCCの夏合宿に行ってきました。

所用で3時間ほど遅れて参加したので、他のメンバーはもうチームを組んで実装を始めていて、さて何をしようかというところだったのですが、LPC1768を4台ほど持ってきていたので、これで何かつくろうということになりました。

しばらく一人で何を作るか思案していたのですが気づいたら夜でした。そこでテーマを決めたはずが実現可能性がゼロに近づきすぎてしまったicchyrさんに声をかけてもらい、シェルスクリプトテトリスを実装してmbedコントローラーをつかって操作しながらBGMとして圧電スピーカーでテトリスのBGM(A-Type)を流すプロダクトをつくることになりました。ちなみにこのBGMは原曲がありましてロシア民謡のコブロチカという音楽が元になっているそうです。

テトリス部分をicchyrさん、スピーカーやコントローラーを私という分担にしました。

実装

コードは下記のURLに置いてあります。

speaker_test - a mercurial repository | Mbed

f:id:gurapomu:20170828231029j:plain

楽譜はicchyrさんに耳コピで作ってもらいました。

icchy (@icchyr) | Twitter

悲しいかな

たまたま持っていた圧電スピーカーなのですが、ものが悪すぎてとある周波数で共鳴を起こして正直言って不快な音が出ます。

秋月で部品を買うともらえるベージュ(?)の紙袋を上からかぶせてテープでとめたらわりとマシに聞こえたのでそんな感じで使うことにしました。

高周波帯のノイズが混じっているような気がしたので、即席でコンデンサと抵抗を使ってカットオフ周波数数十kHzのLPFをつくって噛ませていたのですが、たまたま外してみたらそっちのほうがいい音がでてたのでがっかりしました。今考えたら設計をミスっていたかもしれません。

f:id:gurapomu:20170828233140j:plain

解説

正直お酒を飲みながら実装していたので今見るとひどいコードな気がしますが反省をふまえながら解説していきます。

コアになってくるのはTickerと呼ばれる繰り返しタイマー割り込み機能です。圧電スピーカーの入力にsp1を設定し以下の関数をt秒ごとに繰り返すことで周波数1/2t[Hz]の音がなるという寸法です。

void tick(void)
{
    sp1.write(oto);
    oto=!oto;
}

反省点が2つあります。

1つは変数名が"oto"とかなっているところです。よく考えるとわかりやすくて短い変数名だし悪くないのかもしれません。※諸説あります

2つ目ですが上記4行目の oto=!oto; です。

これは=!の順番が変わるだけで全く意味をなさないコードに変わってしまうものなので要注意だと思っています。

実はこのコード、はmbed_blinky(テンプレートのLEDを点滅させるプログラムです)にも乗っているので本当に良くないと思っています。絶対に書くまいと思っていたことなので非常に反省しています。

反省が長くなりましたが次に行きます。

float mm[]={659,2,493,1,523,1,587,2,523,1,493,1,440,2,440,1,523,1,659,2,587,1,523,1,493,3,523,1,587,2,659,2,523,2,440,2,440,2,0,3,587,2,698,1,880,2,783,1,698,1,659,2,0,1,523,1,659,2,587,1,523,1,493,2,493,1,523,1,587,2,659,2,523,2,440,2,440,2,0,2,659,2,493,1,523,1,587,2,523,1,493,1,440,2,440,1,523,1,659,2,587,1,523,1,493,3,523,1,587,2,659,2,523,2,440,2,440,2,0,3,587,2,698,1,880,2,783,1,698,1,659,2,0,1,523,1,659,2,587,1,523,1,493,2,493,1,523,1,587,2,659,2,523,2,440,2,440,2,0,2,329,4,261,4,293,4,246,4,261,4,220,4,207,4,246,4,329,4,261,4,293,4,246,4,261,2,329,2,440,4,415,8};

これはBGMの譜面になります。0と偶数のインデックスで周波数、奇数インデックスで伸ばす回数を表しています。酔っていたのでこれぐらいの制御方法しか思いつきませんでした。float配列なのに整数リテラルを代入していてこれほど意味のないことはありません。反省です。

for (i=0;i<sizeof(mm)/sizeof(float);i+=2) {
    timer.attach(&tick,1.0/mm[i]/2.0);
    wait(0.2*mm[i+1]);
    timer.detach();
    sp1.write(0);
}

ここが再生部分です。先程説明した繰り返しタイマー割り込みを1.0/mm[i]/2.0秒間隔で動作させるようにしています。これによって周波数mm[i]の音を発生させることが出来ます。このTickerを仕込んで0.2*mm[i+1]秒後にTickerを停止させます。その後スピーカー入力を0にします。

最初はsp1.write(0);を書かずに鳴らしていたのですが、休符の際に2回に1回高音を発するというバグを踏んだので書きました。原因は未だにわかっていません。

よく考えたら1.0/mm[i]/2.0はゼロ除算になると思うんですけど浮動小数点数だとエラーにならないんだっけ???? ARM特有かもしれません。これが謎の高音はこれが関係していたかもしれない。

再生部分は大体こんな感じです。超単純

あとは、もう1台のmbedから信号を受け取って音楽を一時停止させたり停止させたりする機能を実装しています。なんで1台に統合しなかったんだろう、とても不思議です。

おわりに

結局テトリスは完成しませんでした。

シェルスクリプトでは無理がありすぎて2日目の朝からmbed(C++)で実装していたのですが流石に無理がありすぎるということです。

ハッカソンとはなんなのかということにもなってきますが、ある程度作りたいものをもったままスタートできるとスムーズに開発が進むのではないかと思います。