スマホで演奏・作曲できる Web アプリ「Yubi-Jam Music」を個人開発したときにハマったことまとめ
はじめに
作曲初心者のkokemaruです。
記事を見ていただきありがとうございます。
作曲をしていると、忙しい毎日の中でも、ふっとフレーズが降りてくるとき、ありますよね。
でも、音が出せない場所だったり、手元に楽器がなかったりして、気づけば忘れてしまう。
そんなこと、ありませんか?
通勤電車の中でも、昼休みの5分でも、寝る前のベッドの中でも。
スマホさえあれば、どこでも演奏や作曲ができることを目指して、Yubi-Jam Music というブラウザ楽器アプリを作りました。
- ブラウザで動作するので インストール不要
- 左親指でコードパッド、右親指でメロディ鍵盤 を操作
- 登録なし・無料 ですぐにお使いいただけます
デモ動画はこちらです👇
noteに紹介記事を書きました。機能の詳しい説明については、そちらをご覧いただければと思います。
Zenn の方では、開発に至った経緯や、実際に遭遇した技術的な問題とその対処について書いていきます。
自分としては、全く開発したことのない分野での手探り状態の開発だったので、大きなチャレンジでした。時間短縮のために、ChatGPTもかなり活用しました。
開発経緯
もともと作曲や耳コピのために、iPhoneにピアノアプリを入れて使っていました。
録音機能がなかったので、画面録画を使っていました。
その中で、次のような点が気になりました。
開始・終了音
iPhoneで画面録画を開始・終了する時、ポンッと音がしますよね。
イヤホンをしていても本体から鳴ります。(日本だけかもしれません)
盗撮防止などの対策だと思うのですが、通勤中に鳴ると誤解を招きそうで、大変困ります。
写真アプリのライブラリに混ざる
録画した動画が、写真アプリのライブラリに他の写真と混ざってしまいます。
作曲用のアルバムを作って入れているので、録画した動画だけを見ることはできますが、ライブラリからだけ消すということはできませんでした。
そうなると、他の写真を見返すときにどうしても邪魔に感じてしまいます。
ルート音だけでなく、コードの響きで作曲したい
ピアノアプリを使った場合、狭い画面では左手はルート音を単音で押さえるだけで、それに対してメロディを重ねていました。
コードは曲の世界観を形作るものだと思っているので、できればコードを演奏しながらメロディを作れる方法はないだろうかと考えていました。
このあたりをいい形で改善する方法がないかな……と思い、
「それならいっそ、自分で作ろう」と思ったのが出発点でした。
一番大切にしたかったこと
Yubi-Jam Music を作るうえで、一番大切にしたかったのは 「楽器としての演奏感覚」 でした。
あまりに自動化してしまうと、ボタンを押すだけで音楽が流れる「DAW の再生ボタン」のような体験に近づいてしまいます。便利ではあるのですが、「自分の手で演奏している感覚」が薄れてしまうのが嫌でした。
Yubi-Jam Music では、コード自体はボタン一つで鳴らせますが、
- どのタイミングでコードを切り替えるか
- どんなリズムを刻むか
といった部分は、すべて自分の指で決めます。
この「決める余地」が残っていることが、楽器としての楽しさにつながっているように感じています。
Yubi-Jam Music は、スマホの中の小さな鍵盤楽器 を目指して設計しました。
様々な課題
何を使って発音すればいいのか問題
全くの手探り状態で始めたので、「そもそもどうやって音を出せばいいのか」というところから分かっていませんでした。
ChatGPTに相談しながら調べてみると、soundfont-player というOSSが見つかりました。
こちらを使って開発を始めることにしました。
ある程度開発が進んでくると、グランドピアノのサウンドフォントでは物足りなくなってきました。
さらに、この時点で気づいたのですが、soundfont-player はすでにアーカイブされていて、メンテナンスが終了していました。(気付くの遅い…)
調べてみたところ、同じ開発者の方が後継として smplr というOSSを現在も開発されていることがわかりました。 こちらはsoundfontの再生だけでなく、サンプリングのグランドピアノやエレピも使うことができます。
使用ライブラリを smplr に変更し、音色の変更にも対応させました。
サウンドフォント間での音量まちまち問題
色々な音色を使えるようになったのはよかったのですが、試してみたところサウンドフォントによって、発音の音量がまちまちであることがわかりました。
これでは、せっかくコードパッドと鍵盤で別々の音色を選択できるようにしたのに、使い勝手が悪いです。
全体設定に、コードパッド、鍵盤ごとの音量スライダーを用意することで対応しました。
多様なコードを演奏したい
コードパッドのボタンの数は、スマホで演奏することを考えると、サイズ的に 12 個が限界だろうと思いました。
ダイアトニックだけであれば十分ですが、このままだと M7 や m7、sus4 など、よく使うコードを演奏することができません。
これに対応するため、拡張ボタンでコードパッドを切り替える 方式を考えました。
拡張ボタンを押すと、コードパッドの中身が入れ替わり、さまざまなコードを演奏できるようになります。
ところが実際に試してみると、コードパッドは左親指、拡張ボタンは右親指を使うことになるため、
拡張ボタンを押している間は、右親指で鍵盤を弾くことができません。
作曲するために作り始めたのに、「コードだけしか録音できない」という状態になってしまうと、本末転倒です。
この問題はかなり悩みました。アプリ自体の存在意義も危うくなりかねないからです。
そこで解決方法として思いついたのが、MIX 録音でした。
- まず、コードパッドの演奏だけを録音する
- その録音を再生しながら、メロディを鍵盤で重ねて録音する
というフローにすれば、複雑なコード進行の演奏とメロディの演奏を両立できるようになります。
考えてみると、僕自身も「決めたコード進行に対して、何度もメロディを当てて試していく」という作曲スタイルだったので、この形は自分のワークフローにも合っていると感じました。
なお、メロから作曲される方もたくさんおられると思います。
先にメロを録音し、あとから MIX 録音 でコードをあてることも可能です。
転調に対応したい
J-POPでは、Bメロやサビで転調することがよくあると思います。
Yubi-Jam Music でも、ぜひ転調に対応したい!ということで、操作方法を検討しました。
- どうすれば狭いスマホの画面で、操作ミスせずに転調させられるか
- 演奏中でも、スムーズにもとのキー(ホーム)に戻せるか
この2点を考えた末に、user コードパッドの 10, 11, 12 を転調ボタンにする 方式にしました。
そのうちひとつは、元のキー(ホーム)に戻すボタンとして割り当てています。
録音中の転調も記録され、再生時も転調が再現されます。
一方で、「転調は使わず、12個すべてコードとして割り当てたい」という方もいると思います。
そのため、全体設定から「転調ボタンとして使う」か「通常の user コードパッドとして使う」かを切り替えられるようにしました。
正しい発音かのチェック
僕には絶対音感はないので、コードパッドを演奏したとき、正しい音を鳴らしているかどうかの判断が自分ではできませんでした。
そこで、発音時にログへ MIDI 番号を出すようにして、
「このコードに対して構成音が正しいかどうか」を ChatGPT にチェックしてもらうワークフローにしました。
ChatGPT さん、ありがとう。
録音開始直後の音が記録されていない問題
ある程度開発が進んだところで、実際に録音を試して気づいたのですが、カウントイン終了後、最初の1拍目の音が録音されていないことがありました。
ログを調べてみたところ、1拍目よりも少し早くコードパッドを押しており、そのために録音区間外となって切り捨てられていました。
人間の(僕の)精度だと、これを切り捨てられるのは厳しいです。
対応方法を検討し、最初の1拍目よりも0.5拍前からを録音区間として記録するようにしました。
再生ボタンを押された際も、すぐに再生するのではなく、0.5拍早くから再生するようにしていました。
これで、再生ボタンを押されてから1拍目に音がなり始めるようになりました。
あとで調べてわかったのですが、この機能は プレロール といって DAW でも採用されているようです。
もっと早く調べればよかったです…
再生時に音がずれる問題
負荷の状態によって、再生したときに音のズレが発生することがわかりました。
調べてみると、setTimeout で再生を制御していたことが原因のようでした。
そこで、Web Audio API の AudioContext.currentTime を基準に、予約再生(スケジューリング)する方式に変更し、改善しました。
クオンタイズ
DAW でクオンタイズをかけると、ノートがグリッドに合うのは良いのですが、
元のノートが書き換わるので、拍指定を間違えると Undo する必要がありますよね。
スマホの小さな画面で「元に戻す」操作を何度もやってもらうのは、使い勝手があまり良くなさそうだな……と思いました。
そこで、
- 録音データそのものには手を入れずに残す
- 再生時にだけクオンタイズをかける
という方式にしました。
これなら、何回でもクオンタイズの設定を変えられますし、
「やっぱりクオンタイズなしで聴きたい」と思った時にもすぐ戻せます。
さも自分で発明したように書いていますが、たぶん似たような機能は DAW にもあると思います。
実機でのログ問題
実機で動かすとき、PCとは違った挙動をする場合がありますよね。
そんなときに大変助かるのがログです。開発作業にログは欠かせません。
Mac であれば、iPhone を接続してインスペクターやログを取得できるそうですね。
僕は Windows で開発しています。はい。
有料のサービスもありましたが、色々と検討した結果、「とりあえずログさえ送れればいいか……」と割り切り、 WebSocketを使ってログを転送する仕組み を作りました。
ルーペ・テキスト選択問題
演奏する都合上、鍵盤を長押ししたり、素早くタップを繰り返したりします。
その際に、
- 長押しでルーペが出る
- 連続タップでテキストが選択されてしまう
といった現象が発生しました。
これらを避けるために、対象要素では touchstart / touchend などのイベントで preventDefault を呼ぶようにして、
ブラウザ標準のテキスト選択やルーペ表示を抑制するようにしました。
URLバー問題
最近の iOS で、画面を横向きにしたときに URLバーが表示されたりされなかったりする現象がありました。
しかも、少しだけスクロールするとバーが出てきてしまいます。
Yubi-Jam Music は「スクロールせずに固定」で使う前提で作っています。
そのため、演奏中に画面がスクロールしてしまったり、突然レイアウトの高さが変わったりすると困ります。
色々と検証した結果、
- 画面のローテーション(向きの変更)を検出したときに、ページをほんの少しだけスクロールさせる
という方法で、URLバーの状態を安定させるようにしました。
タッチ領域問題
スマホの狭い画面で演奏するので、なるべく画面をいっぱいに使いたいと考えています。
そのため、鍵盤やコードパッドの高さも、最初は画面ギリギリまで使っていました。
軽くテストしているときは問題ありませんでしたが、
本格的に使い始めてみると、時々 鍵盤やコードパッドの下部が反応しなくなることが判明しました。
イベントをキャプチャしてログを出すようにしましたが、そもそもイベント自体が発生していません。
おそらく OS 側でハンドリングされているパターンがあるのだと思います。
仕方ないので、コードパッドと鍵盤の下部を少し上げることにしました。
ただ、そのままだと見栄えが悪いです。
そこで、空いた下部のスペースに 歌唱域 や 現在のキー / BPM などの情報を表示するようにしました。
結果的に、「実用情報が増えたステータスバー」に落ち着きました。
PWA問題
スマホの狭い画面で(ry
本当は PWA で URLバーなし・全画面で使える状態 にしたいと考えていました。
iOS の Safari で「ホーム画面に追加」をすると、PWA として動作します。
最初はこれで問題なく動いていました。
ところが、
- iOS をスリープさせる
- アプリを再起動する
といったタイミングで、音が鳴らなくなってしまうことがわかりました。
原因を調べるためにログを見ようとしたのですが、なぜか PWA モードでは、
先ほど実装した WebSocket ログがうまく動かず、ログが一切出ません。
仕方ないので、デバッグ用として「実機でログをダウンロードできる仕組み」を追加しました。
調査の結果、
- Web Audio が
suspendからrunningに戻らない - AudioContext の再初期化もうまくいかない
という状態になっているようでした。
作り直してもダメで、ネット上でも同様の報告が見つかり、現時点では改善されていないようです。
そのため、いまのところ iOS の PWA モードはサポート外 としています。
最後に
まだ他にも色々と苦労したポイントはあった気がしますが、
ひとまず思い出せたところを書いてみました。
どこか一つでも、誰かの開発のヒントになれば幸いです。
そして、もしよければ実際に Yubi-Jam Music も触ってみてもらえると嬉しいです。
Discussion