鼻歌から曲を生成するアプリ「HUMOR」の紹介
ソースコードは https://github.com/shiwasu-tech/humusic です.
ソースコードの解説は時間があるときに追記します.
概要
皆さん音楽は聞きますよね?
では,音楽に携わったことはありますか?
義務教育でリコーダーに触れる機会以降,楽器を触る人は多くないでしょう.
音楽制作している人なんてなお少数ではないでしょうか?
音楽を奏でる,音楽を創るといったことは,最初のハードルが高く,始めるには腰が重く感じるものです.
そんな音楽体験を,誰でも気軽にできたら面白いのではないか?というコンセプトで立ち上がったのが,このHUMORのプロジェクトです.
さて,私が所属するプログラミング部のC0deでは2024/12/04~12/20にかけて,ハッカソンを開催していました.
『第2回 C0deハッカソンwith pixiv』と題して
— ピクシブ株式会社 pixiv Inc. (@pixiv_corp) December 4, 2024
名古屋工業大学公認課外活動団体C0deさんとの
ハッカソンに今年も協賛します📣
本日から約3週間、3名のエンジニアメンターが参加し、
学生の皆さんを全力でサポートしていきます💪
お互いに学び合い、挑戦を楽しむ時間になることを願っています。… pic.twitter.com/sEkInswLSw
ちょうどいいタイミングだったので,このハッカソンでHUMORのアイデアを具現化させることにしました.
作るもの
先に,音楽体験はハードルが高く感じるとは言いました.しかし,誰でも簡単に音楽を奏でる方法があります.
そう,鼻歌です.
とは言え普通は,鼻歌で歌えるのは,知っているフレーズや即興的なメロディー程度に限られるでしょう.鼻歌だけで音楽を作り上げるのは至難の業です.
そこで今回使用するのが,最近流行りのAIです.
以上の「鼻歌 × AI」を組み合わせることで
鼻歌を音階に変換
AIが続きを生成する
の2つによって構成される
鼻歌の続きを生成するAI「HUMOR」
を作ることにしました.
変換側
相方が担当(詳しくはリンク先)
鼻歌を音階に変換する部分は,相方が担当したので,気になる方は彼の記事を参照してください!
概要だけ説明しておくと,
鼻歌音声を録音
ピッチとテンポを解析
音階に変換し,midiデータとして出力
という処理を行っています.
生成側
こちらは私が担当しました.
プロンプト(文章)から音楽を生成するsunoAIなどが既にはやっているように,音楽生成AIというジャンルには,世界中で多くの人が挑戦しており,完成度の高いプロダクトが数多く存在します.
「続きの生成」という観点でも既存の物があり,代表的なものに,GoogleのMusicTransformerがありました.当初はこのライブラリを使う予定でしたが,GoogleColabで試していた際,対応するTensorFlowのバージョンがとても古い,モデルが非公開になっている,大量のエラーに対処しきれなかったなど,ハッカソン期間内に上手くいかない予感がしました.
「だったらもう自分でAIのモデル作った方がよくね?」と思い,方針転換しました.
データセット
モデルを自分で作ると言っても,データセットがないと無理があるので,フリーの音源データが転がっていないか探しました.
限られた学習時間とAIの表現性の兼ね合いから,音楽のジャンルはオーケストラに絞ったところ,使いやすそうなのは
The Masetro Dataset
MusicTransformerで使われたデータセット
1000曲以上
Classical Piano Midi
300曲程度
ピアノだけ(左右ピアノが別トラックになっていた)
の2つがありました.
いろいろ試しましたが,最終的には,曲数が少なく,左右のピアノが分割可能だったClassical Piano Midiを採用しました.
複雑な音を生成する予定はなかったので,右手ピアノのトラックだけを抽出して学習データにしました.
また,曲における音の数はバラバラで,学習させるにはシーケンス長が整っていなかったので,特定のシーケンス長に分割しました.なおこれはtokenizerの機能にあったので一括で分割しました.
音のトークン化
曲を学習させる上で,メロディー細かくデータに分割する必要があります.
今回は,midiをトークン化するライブラリであるmiditokを用いました.トークン化手法はREMIを使いましたが,あまり理解していません.デフォルトだったので採用しました.
トークナイザの設定ファイルとトークン化したデータを見た感じでは,midiのメッセージそれぞれにIDを割り振って変換するというものだと思います.
tokenizerの機能で,トークン化したデータ列をpytorchのdataloaderクラスに格納することができたので,まとめてそこの処理もやってます.
#tokenizer & dataloader
順次ソースコードを追加します.
モデルの構築
深層学習モデルの構造は,当初MusicTransformerを使おうとしていたこともありTransformerを試しました.また,表現性には欠けますが,Attention機構を組み込んだLSTMをEncoder-Decoder構造にしたモデルも試しました.

#LSTM Auto-Encoder with Attention
順次ソースコードを追加します.
学習方法
miditokにより,学習データとなる複数の時系列データが用意でき,モデルも用意できました.これを用いて学習させるわけですが,いかにして学習させるか,最終的に以下の2つほど試しました.
①.教師データにマスクをかけ,「冒頭」を入力データにし,「続き」を正答データとして与える.
②.マスクを掛けずに同じデータを入力データ,正答データとして与える.


学習結果
「続きの生成」という目的でやっているので,①の方法が適切なのだと思います.ですが,ハッカソン期間中には①の方法ではあまりいい結果が生成されませんでした.LSTM,Transformerのどちらも,無音だけで終わる,定期的に拍を打つ音のみが生成されるといった結果でした.
②の方法では学習時に明らかにlossの値が減少しており,まともなデータが生成されることが期待されました.
試しに生成してみたところ,Transformerは,入力したデータがそのまま複製されるだけとなってしまいました.
(同じデータを与えてたら,そうなりますよね...Transformerの優秀さに改めて感激しましたが,ハッカソン期間内に修正できる気がしなかったので,不採用です.)
一方で,LSTMの方はそれなりに(諸説あり)音らしい音が生成されました!
ハッカソン期間も鑑みて,最終的にLSTMのモデル構造と学習方法を使用することにしました.
ハイパーパラメータ
操作したパラメータは主に
・LSTMの隠れ層の次元
・シーケンス長(1つの時系列データにおけるトークン数の最大値)
・バッチサイズ(バッチ学習のための分割数)
・epoch数
です.
何度も言っていますが,ハッカソンで時間がなかったので,試して上手くいったものを採用していきました.
LSTMの隠れ層の次元はモデルの表現力に大きくかかわるので,ひとまず512次元を試しました.512次元だとさすがに学習時間が長かったので,複数の値を試した中で上手くいく割合が高かった200次元を採用しました.
シーケンス長はデフォルト値であった1024トークンにしました.
バッチサイズは,大きすぎるとGPUのメモリ上限に引っかかってしまったので,メモリ上限ギリギリの値として32に設定しました.
epoch毎にモデルを出力してテストしていたのであまり考えていないです.
試行回数が少ないので,もっといろんな値の組み合わせをすべきだと思っています.いつか試します.
#train
順次ソースコードを追加します.
生成例
ちなみに生成は学習と同じように,入力データを与え,モデルを順方向に処理させることで生成させました.
上手くいった例はこちらです.
隠れ層:200次元,epoch数:28
入力データ:bach_850の右手ピアノ(J. S. Bach: Prelude and Fugue No. 5)

上手くいかなかった例はこちらです.
隠れ層:200次元,epoch数:29
入力データ:きらきら星(鼻歌変換)

上手くいった方の生成結果を見てみると,メロディーが成立している部分もあります.また,既存の楽曲にありそうなメロディーや,小節ごとに1度拍を打つだけの部分も含まれており,課題点が多々あります.また,生成した曲全体を聞いていると,全体的に同じ音の繰り返しがあり,LSTMとしての限界に近いものを感じます.
また入力が,既存の楽譜など,”綺麗な”音源であるほどうまくいく傾向がありました.もしかすると,プロンプトエンジニアリングが必要かもしれません?...
このようにまだまだ,改良の余地はありますが,ハッカソンとしてはこれで出しました.
アプリケーション化
ハッカソンだったので見栄えを考慮して,アプリケーション化もしました.
pyside6というGUIライブラリを使ってこんな感じの見た目にしました.

個人的に,録音/変換/生成などの処理中に,ステータスバーが動くような処理を追加したかったのですが,async,threadingなどの知識不足で叶いませんでした.
相方の意見で,exe化も試しましたが,ライブラリの依存関係などで失敗しました.冷静に考えると,モデルのデータがでかすぎることもあって非現実的だったかもしれません...
ハッカソン,結果
ハッカソンの開発期間は16日間でした(発案と技術の検討は事前に行いました.)が,そもそも私がまだpytorchを学んでいなかったので,序盤1週間ほどはpytorchの学習に充てていました.そんなこんなで実質10日ほど(学校の課題もあったので実際はもっと少ないかも)でこれだけのものを作り上げられたのはまぁよかったなと思っています.
ですが,ソースコードの多くが,pytorchの教材内で自然言語処理に用いられていたコードを参考にしています.音楽xAIといった題材の教材を参考にすれば最終日までに少しいいものが作れたかもしれませんが,後の祭りですね.
ハッカソン自体は,C0de部から10チーム30名ほどの参加があり,各チームレベルが高かったです.
全参加チームの製作物はこちらから見れます.
先日、「第2回C0deハッカソン with pixiv」の最終発表会が行われました!どのチームも無事発表までやり切ることができました!これから全10チームの作品を紹介していきます! pic.twitter.com/16AosFonaq
— 名工大プログラミング部C0de (@c0demattari) December 23, 2024
HUMORは,
メンター賞
をいただくことができました🎉
最優秀賞,優秀賞を逃したのは結構悔しかったです.
最終発表会では,展示会としてプロダクトを直接見せ合う時間もあったのですが,HUMORを触ってもらった感想は,「すごい!音楽っぽいところある!」「う~ん🤔(微妙)」「過学習しちゃってるかもね~」など様々でした.
展望
現段階では,当初の目標が達成できているとはまだ言い難く,短期間でひとまずプロトタイプが完成したというレベルです.Transformer含め,深層学習モデルの検討や,録音変換側の精度向上などまだまだ改善の余地がありますので,今後もプロジェクトを続けてより良いものにしていく予定です.
また,実際の楽器と組み合わせることで,鼻歌の続きをリアルタイムで生成&演奏してくれる楽器などが作れたら面白そうだなと思っています.
興味がある方は,https://x.com/shiwasu_tech まで連絡いただけるとありがたいです.
まとめ
「誰でも音楽に携われるようにしたい」という考えのもと,誰でもできる鼻歌から続きの音楽を生成するAIを作成しました.フリーのオーケストラmidiをmiditokによってトークン化し,pytorchでLSTMに学習させました.鼻歌をmidiに変換して入力とすることで,midiを生成させました.まだ”続き”とは言い難いレベルですが,音楽を生成させることができました.
まだまだ改善の余地がある上,他のプロジェクトへの流用などより良いプロダクトを作ることを検討しています.
GitHub
※humusicは当初の名前です.