Androidアプリ「こえめと-声で操作するメトロノーム」を公開しました
「こえめと」は、楽器を両手で持ったまま、声で操作できるメトロノームです。
現在、GooglePlayでオープンテストという形で公開しています。アプリ(Androidのみ)は次からダウンロードできるので、興味のあるかたは使ってご意見等をお寄せください。
https://play.google.com/store/apps/details?id=mashro.com.wixsite.mashro.km03
趣味でアプリを作っているアマチュアなので、最小限の機能のみ搭載したシンプルな作りですが、それがかえって使いやすいように思います。
(その後、全面公開としました。また紹介動画をYouTubeにアップしました。)
https://www.youtube.com/watch?v=cYpEThvyyZI【特徴】
・30~300の数字を言うとそのテンポでメトロノームが始動し、「止まれ」というと止まる。
・図形が回転し、視覚的にもテンポをつかみやすい。
・市販電子メトロノーム並みの精度(時間間隔の誤差の標準偏差±0.15%)。
・無料で広告もなし。データの保存も外部送信もないのでプライバシーの問題もなし。
・私自身、チェロの練習に使っているが、結構役立つと思う。
・メトロノーム自体の音で音声認識が邪魔されることがあるので、外部スピーカーかイヤホン等を推奨するが、それらがなくても使える。
--------------------
開発の経緯
1年半ほど前からチェロを習い始め、練習のたびにメトロノームアプリを使うのだが、スタートやストップ、テンポの変更をするたびに弓を置いて操作しなければならないのが煩わしかった。楽器を持ったままで音声で操作できるメトロノームくらいあるだろうと探してみたが、見当たらない。そこで、自分で作ることにした。
音声認識
まず、音声認識である。Androidには、SpeechRecognizerというクラスがあって、これを使うと簡単に音声認識できるらしい。
https://qiita.com/kaleidot725/items/8fe6b6f137a9a8710d21
こういったサイトを参考にして自分でやってみると、確かに簡単に、音声を文字にすることができた。ただし「ボタンをタップして音声入力する」という使い方しかできず、「ずっと待機状態にしていて音声が入力されたらそれを認識する」という使い方(連続入力)できない(いろいろ模索したがだめだった)。手を使わずにメトロノームを操作するために音声入力したいのに、ボタンをタップしなければならないのでは意味がない。
さらにいろいろ探してみると、Voskというライブラリがあって、これを使うとオフラインで連続入力ができるという。
https://dev.classmethod.jp/articles/kotlin-android-vosk/
Voskのモデルは各国語別にある。日本語は48MBのものと1GBのものがあるが、スマホに入れるので48MBの軽量モデルを使うことにする。さらに、これの使い方が最初は全然わからなかったが、次のサイトにあるデモプロジェクトを真似して、連続入力のアプリを作ることができた。
https://alphacephei.com/vosk/android
ただし、認識の精度はあまりよくない。例えば、テンポを92に設定したくて「きゅうじゅうに!」と言ったとしても、「九十に」などと認識されてしまう。メトロノームを停止させようと「とまれ!」と言った場合は「泊まれ」と認識されることが多い。そこで、誤認識のパターンを調べ上げ、例えば「九十に」と認識されたらこれを「九十二」に変換するようなコードは自分で作る必要があった(さらに、Voskは漢数字で出力されるのでこれをアラビア数字に変換することも必要)。なお、日本語モデルは和語のほうが相性が良いのか、「ストップ!」よりも「とまれ!」のほうが安定して認識されるようだ。
音を同じ周期で発するという問題
次に「音を同じ周期で発する」である。これは、Androidのプログラミングをする人ならだれでも、まずはHandlerを使って定期的な処理を行うとか、timerを使うとか考えるのではないだろうか。私も最初はそう思って、HandlerとRunnableで1秒おき(つまりM.M=60)に音を発することを考えた。MediaPlayerのほうがなじみがあるが、SoundPoolのほうが正確なタイミングで音を出せるというので、それを使うといいと思った。ところが、実際にやってみると、私ごときのリズム感でもはっきりわかるくらい、テンポが一定しない。録音してAudacityで見てみたが、テンポを速くするとたぶん10%くらいの誤差がしょっちゅうあった気がする。これでは使えない!
では、HandlerとRunnableを使わないで正確な周期を実現する方法はないかと考えて、次のようなコードをおもいついた。
--------------------------------
while( System.nanoTime() < 音が鳴るべきタイミング){
}
soundPool.play(soundId, 1.0f, 1.0f, 0, 0, 1.0f)
sharedPref.edit().putInt("timeOfSound", System.nanoTime() )
--------------------------------
音が鳴るべきタイミングに達するまでwhileのループが(何も処理をしないで)回り続け、そのタイミングが来たら次の行の処理(音を鳴らす)に進むとともに、その時刻を記録するというものである。
こうすると、記録された時刻は非常に正確に、音が鳴るべきタイミングを示していたのでこれでよいかと思われた。しかし自分の耳で聞いてみるとタイミングがふらついているように聞こえた。これは自分の耳の問題、あるいは気のせいであろうと思ったが、念のためAudacityで録音して、画面上で間隔を調べてみた。すると、実際は相当の誤差があった。これはたぶん、スマホがsoundPool.playの指令を出すタイミングが正確でも、そのあと実際に音が出るまでにはいろいろあって、その部分で誤差が出るのだろう。
次の記事などに、メトロノームアプリには誤差があるものが多い(そうでないものもあるが)と書かれているが、誤差のあるアプリはこうした問題があるのかもしれない。
https://ihatomo.hatenablog.com/entry/2016/11/19/140923
図形を連続的に動かす
さらにもう一つの課題は「図形を連続的に動かす」ということである。スマホアプリの多くは音が出るだけ、もしくは色が点滅するだけで、図形が動くということがない。あるチェロの先生が、アプリでなくぜんまいのメトロノームを推薦していてその理由は「視覚的にテンポを感じられる」ということだったので、この機能をぜひ実現したかった。
図形の動きとしては、通常のメトロノームのような往復運動でも良いが、何か独自のものをと考え、円がくるくる回転する(1回転で1ビート)ような動きを採用した。
これを動かすために、まずアニメーションの機能を使ってみた。しかしテンポが正確にならない。次にはスマホの時計(ナノ秒まで計測できる)を使って時間に応じて図形を回転させることをやってみたが、A⇒B⇒Cと移動してほしいのに、Bが省略されてA⇒Cと移動するように見えるなどのことが発生し、ぎくしゃくした動きになってしまった。
スマホというのは、タイミングに関してはかなりいい加減な機械なのだ、確かにこういう厳密なタイミングが必要な場面なんてスマホにとってはないのだから、ハードウエアまで考えることができる高度なプログラマーならいざしらず、私のようななんちゃってプログラマーには無理なのだろう・・・と思われたとき、スマホでmp4などの動画を見ることを思い出した。mp4の動画は普通に、テンポよれよれでなく再生できているではないか!
何のことはない、図形が動いているようなmp4の長い動画を作って、それを再生させればいいではないか! ということで、動画再生の方法を調べると次のようなサイトがあった。VideoViewというものを使うと簡単に動画再生ができるという。
https://qiita.com/kaleidot725/items/8fe6b6f137a9a8710d21
確かに、簡単に動画再生ができた。そこで、VideoPadを使って、図形が動くとともに音が出るようなmp4の30分動画を作った。これを再生する際には、テンポの誤差はほとんど出なかった。
最初の実用的なアプリ
これで材料は整った、Voskを使って音声認識を行い、「はじめ」「止まれ」と30~300の数字が音声入力されたら、それに応じた速度で動画を再生/停止するというプログラムを作ればよい。この部分はとにかく手を動かせばできていく。
さて、これで一応、使えるようなアプリができた(実際、練習で使うとけっこう使えた)が、問題はアプリが重いという点である。なにせ、30分の動画がアプリの中に組み込まれているので、150MBくらいのアプリサイズになっていた。これを何とか小さくすることはできないか。
アプリの改良
まず考えたのは、動画を短くして繰り返し再生させるということである。VideoViewでは繰り返し再生という設定ができないが、SetOnCompletionListnerを使い動画が終わったら再度再生を開始するというふうにすることはできるので、これをやってみると、動画の継ぎ目にかかる時間がどうしても一定せず、その部分でテンポが乱れてしまう。次に、MediaPlayerにはLoopという設定があるので、これを使って繰り返し再生をしてみたが、同様の問題が発生した。やっぱりこのやり方はだめか・・・と思ったところでやはり発見したのが次のサイト。
https://qiita.com/kaleidot725/items/9121301a35ff2c19a9d4
このサイトで初めて、ExoPlayerというものがあって動画再生等に使えることを知った(なんという無知!)のだが、これによると、MediaPlayerでは2つの動画をつなげて再生したとき、その間に少し隙間ができるのだが、ExoPlayerではぴったりくっついて隙間時間なしで再生できるのだという。そこでExoPlayerを調べ、短い動画を繰り返して再生するようアプリを作り替えようと考えた。
まず1秒に1回音が鳴り図形が回転するような動画を作り、これを繰り返し再生することを考えた。これはうまくいくように思われた。実際には周期が1000msであるような動画を作っても再生時には1020ms程度の周期となったが、これはほぼ安定しているのでアプリ側で再生速度を調整することでほぼ正確な周期で音と回転を制御できた。あとはこれの再生速度を変更することでどんな速度にも対応できると思われた。
ところが問題が発生。これではM.M.=120程度(2倍速再生)まではうまくいくが、それ以上の速さにしようとするとうまく再生できなくなる。その理由がわからず苦労したが、どうやら、ExoPlayerは、再生後に停止してまた再生する、といった使い方をするときに、再生から停止まで少なくとも0.5秒程度を必要とするらしい。
ならば、例えば5秒で5回音が鳴る(図形が5回転する)ような動画を作ればいいと思ってそうしたが、これもうまくいかなかった。というのは5秒に1回、動画のつなぎめの部分が20ms程度、どうしても長くなってしまうのだ。20ms、2%の誤差だが、私が聞いても何となくわかるくらいだから、耳のいい人には非常に大きな差に聞こえるだろう。
この問題の発生原因と解決には相当苦労した。原因は、わかってみれば当然のことだが、動画というのは静止画を次々に表示したものなので、動画の長さは連続量ではなくコマ送りのタイミングによる離散的なものであるという点である。そのため、動画を作るにはVideoPadを使っていたのだが、エクスポートされる動画の長さは5000msちょうどになることはなく、4992msの次は5013msになるなどということなのだ。この4992とか5013とかの値はいつも全く同じではないが、ある条件のもとで動画をエクスポートすると、だいたい今はこういうような値になるというのが見えてくる。そして、VideoPadのクセと思うが4992msの動画をエクスポートすると、たぶん最後に1コマ追加するのだろう、5013msになるのだ。
そこで、今回行ったのは、まず、4992msの長さで5回音が鳴り図形が5回転するような動画を作った。(実際には4992は5で割り切れないので、1回音が鳴って図形が1回転するような動画を、998ms、999ms、998ms、999ms、998msの長さで作って、それをつなげて4992msにする) この動画の最後を10msくらいけずる。そしてVideoPadでエクスポートすると、ちょうど4992msの動画ができる。
最後に、これをスマホで再生する際に、5000÷4992=1.0016倍の再生速度とする。
こうしてできたアプリのサイズは約65MBとなった。スマホアプリとしては大きいが、音声認識モデル48MBを搭載しているので、やむを得ないだろう。
できたアプリの精度
audacityを使ってこのアプリのメトロノーム音を約1分間録音し、その波形から音の間隔のデータをとった(61サンプル)。
すると、平均は999.7ms、標準偏差は1.54ms、最大値は1003ms、最小値は997msであった。標準偏差が1.54msということは、0.15%に相当する。
市販の電子メトロノームの多くにはその精度が書かれていないが、ざっと見たところ書かれているものの精度は±0.1~±0.5%(この数値の意味が標準偏差なのかはよくわからないが)であった。そのうち±0.1%のものは「高精度」が強調されている。なので、このアプリも多くの市販電子メトロノームと比べて悪いほうではないだろう。
-----------------
ということでできたアプリをGooglePlayにアップしています。自分が使う分にはかなり役立っていると思います。楽器をされる方は、ぜひ一度、お試しください。
コメント
コメントを投稿