歌えば作曲完了

2022年3月10日未分類

こんにちは。今回が初めての投稿になります。松井です。
まず、今回の話をする前にですが、今回の話はかなり長くなってしまったため、暇な時間に読んでいただければ幸いです。

導入

皆さんはDTMをご存知でしょうか。
DTMとは、Desktop Music。CubaseやProTools等のDAWソフトを使用して、コンピュータで作曲を行うことを言います。
最近はボカロPやシンガーソングライターが認知されてますよね。
僕はCakewalk by bundlabというWindowsでしか使えない無料のDAWを使用して曲を書いているのですが、率直に言って、もういい加減マウスをポチポチしてピアノロールに打ち込みする作業に萎えてしまったため、何かもう少し楽チンに作曲できる手段は無いのかと考えました。
そこで、カラオケの採点システムみたく、歌をその場で音階へと変換して、それをMIDIの状態にしてしまえば、そのデータをトラックに入れてあげるだけで曲が完成してしまうことに気づきました。
このシステムから受けられる恩恵として、音感0でも、コード理論を何も勉強してなくても、どこか適当な場所で歌って録音してしまえば作曲完了ってわけです。
今回は、そのメカニズムについて紹介していきたいと思います。ただ、そのアルゴリズムの紹介の前に、まずはMIDIについて軽く紹介しておきます。

MIDIとは

MIDIとは音符のデータを記録するファイル規格の一種です。SMF(Standard MIDI File)と言ったりもします。
よく皆さんも画像は.jpgや.png等の規格を使いますよね? 音声だと.wavや.mp3だったり。そんな感じで、作曲のデータは.midに書き込むことができるのです。
MIDIは確か初期バージョンから38年間くらいシステムが更新されてなかったんですけど、何か一昨年の2月にいきなりバージョン2.0が更新されてましたね。その更新分がちゃんと各ソフトに反映されてるのかは不明ですが、一応仕様書はこの公式に載ってありますので、気になる方のために、ここにソースを掲載しておきます。
http://amei.or.jp/midistandardcommittee/MIDI2.0/MIDIspcj2.html

実装法

では、アルゴリズムについて紹介していきます。
結論を言いますと、Chroma-CQTというアルゴリズムを使えば一発です。Pythonではlibrosaというライブラリがあって、その中のlibrosa.cqtというメソッドに波形データとその他パラメータを入力してあげれば、各音階ごとの音圧の強さが時間軸上に表示される二元配列データとして出てきますので、その中から時間系列ごとに最大値のインデックス(一番音圧の強い音階)を拾って一元配列に並べてあげれば完成という仕組みを構想できます。
http://librosa.org/doc/main/generated/librosa.cqt.html
ちなみに、大抵の実装例では、librosa.cqtではなく、librosa.feature.chroma_cqtが使われがちなのですが、こちらの方はn_chromaというパラメータが出力結果の見方をややこしくさせてしまうため、前者を選びました。
http://librosa.org/doc/main/generated/librosa.feature.chroma_cqt.html
ただ、このメソッドに音声ファイルの波形データをぶち込んで終了!ってのも流石に味気ないと思いますので、ちゃんと中の仕組みも解説していきます。
この仕組みを紐解く鍵は、音階律、フーリエ変換、窓関数です。

音階律

僕たちは何かしらの音源(Instruments)を使って作曲するにあたって、当たり前のように和音を組み合わせてコード進行を展開したり転調したりしてますが、本当はあのロジックも、そもそも音階が複音のメロディの組み合わせに対応できるように数列規則が並んでいる必要があります。
そこで現代の僕らが使ってる音階律は十二平均律と言って、どの音階を基準点に持ってきても、その1オクターブ上の音は基音の2倍の周波数である仕組みになっているんです。つまり、オクターブシフトしていくごとに音階の周波数は倍々されていきます。また、こういう風な倍音規則は偶数倍音と呼ばれたりもします。
さらに、1オクターブの中には12個の音階が並んでますので、これを漸化式として一般解を紐解くなら、
F(n+i)=F(n)×(2^(i/12))
ということになりますよね。つまり、普遍的に指数関数が成り立つんです。
そして、実は単にこのロジックだけだったら別に1オクターブが12音階である必要もありません。どの音階の数でも対応させることができます。それが何で1オクターブが12音階になったのかと言いますと、
12個の要素の中央にいるのは何番ですか? 6番と7番ですよね。では、その内の7をiに代入してみてください。
2^(7/12)=1.498…≒1.5
なんです。つまり、なんとこの12音階によって奇跡的に完全五度という音が登場しますので、奇数倍音の組み合わせが非常に容易になり、これによってコード理論が成り立つんです。
やっぱ12って奇跡の数なんですかね。時計でも何でも、やたら12という数は美しいというイメージがあります。

フーリエ変換

次に。フーリエ変換です。まぁ軽く概要を説明していきますと、
まず、音とは簡単に一定周期で振動する波ですよね。で、440Hz, 554Hz, 659Hzの音を合成する。これも簡単なんです。ただ、その合成された波はぐちゃぐちゃな形をしてて、ぱっと見こいつがどの高さの音をどんくらいの音圧でそれぞれ混ぜていったのかとか分かりませんよね。そいつを分解して、各周波数ごとの音圧を見れるようにするよ? という風に変換してくれる魔法の数式がフーリエ変換なんです。で、それを作ってしまったのが、フーリエさんやガウスさん、クーリーさん、テューキーさん等です。

どの楽器に当てはめても良いのですが、基本的に音階の周波数が決まってるからって、どの楽器でもその音しか鳴らないというわけではありません。打楽器や笛その他諸々でハーモニーは違いますよね。これは基本周波数以外の倍音やノイズ等の音が良い具合に複雑に混ざり合ってるからなんです。勿論それは物理的な性質がそうしてるものですし、440Hzの純粋な音しか無かったらただのビープ音になります。
また、こいつの式の証明には、オイラーの等式だの級数展開だのバタフライ演算だの非常に面倒くさい課程を踏まなきゃいけないんですけど、別にこの式の原理を理解する必要はなくて、要はこの数式に波形のデータさえぶち込んでしまえば、合成波を単純波成分に分解できるってことさえ分かれば良いんです。

実際にDTMでは、これをスペクトラムアナライザーにかけて分析し、フィルターやイコライザーの力で低周波の音(ゴーゴーする音)や高周波の音(キンキンする音)を取り除いたり、300~500Hz帯をブーストして深みのある音を作ったり、1~1.2kHz帯をブーストしてブライトな音を作ったりして、ミキシング(音を加工)することができます。

ただ、音声データって要はデジタルですよね。
連続する波でできてるんじゃなくて、符号化や量子化を使って、点々と波の位相を記録していく形になっています。この離散の状態からフーリエ変換するにはどうしたらいいのかというと、その音のサンプルたる集合を、例えば1ブロック1024個という風に分割分割していって、まぁその単体のブロックを本当はフレームと呼ぶのですが、フレームごとに周波数分布を確かめる形でパラパラ漫画式に求めていく形になります。こういったフーリエ変換は、短時間フーリエ変換(STFT)と呼ばれます。今回は波形データをMIDIファイルに出力することが目的ですから、この1フレームを16分音符ってことにすれば良いですよね。また、曲にはテンポってものがあります。曲の流れる速さです。それに応じて1フレームに抜き取るサンプル数を調整していきます。

また、本当はこのSTFTの状態だと、Chroma-CQTではなくChroma-STFTという状態になってしまいます。これを解消するためには定Q変換をする必要があるんですが、これの説明は割愛させていただきます。ただ、これによって、Chroma-STFTとChroma-CQTでは後者の方が変換性能は上がると評価されています。これは実際に僕が試してみてもそうでした。

窓関数

確かに、この音階律とフーリエ変換という2つの法則を組み合わせれば、何となくいけそうだという雰囲気は掴めて貰えたと思います。
ここで、例えば、1フレームの音階推定はA4…つまりラの音(440Hz)だったとします。他のどの音階の音圧成分よりもA4の音圧量に長けていたとします。それをどのような形にして調べれば良いのでしょうか。本当はこのモデルを自力で考察するだけでもかなり奥深いと思います、そこで使われるやり方は窓関数になります。
この仕組みが一番理解しやすい文献は、恐らくscipyという数値計算ライブラリのリファレンスです。実はlibrosaも、このscipyというライブラリをラッピングしています。
https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.windows.hann.html
これは窓関数の中でもハン窓という関数で、窓関数の中でも最も基本的なモデルです。これ以外にもモデルは色々あります。
https://docs.scipy.org/doc/scipy/reference/signal.windows.html#module-scipy.signal.windows
要はこれを使って、440Hz近辺の音だけに周波数の候補を区切って、その音階の強さというのを割り出せば良いんですね。それを各音階ごとに調べていき、最大音圧帯がその音階だと割り出せば良いのです。
勿論、440Hzの音と439Hzの音では、440Hzが一番価値のある音と見られるはずですから、440Hzから離れる音ほど価値の薄いものとしてフィルタリングをかける必要があります。だから窓関数はこういう見た目になってます。
今回僕はハミング窓を使いましたが、別にこのモデルは正直何でも良くて、ガウス窓でもブラックマン窓でも特に音階推定においてそこまで致命的な支障は出ないです。librosa.cqtだとデフォルトがハン窓になってて、Chroma-CQTの活用例では割と三角窓が紹介されてることが多いです。
また、先ほどの音階律を思い出してほしいのですが、音階律って線形関数ではなく指数関数でしたよね。つまり、
220Hz, 440Hz, 880Hz, 1760Hz…と、音階が上がってくごとにその音の陣取る領域って何となく広くなっていくことがイメージされますよね。
そこで、重み付き窓関数というのを利用して、その一つの音が陣取る音域が広くなるほど高さを縮める戦術で上手いことバランスを取る作戦に出てるんです。これに関する最も分かりやすいソースはこちらになります。
Python (LibROSA) で音高 ・クロマ特徴を算出する方法

そんな感じでChroma-CQTアルゴリズムの説明は以上になります。それでは、早速作っていきましょう。

実践

僕のGitHubソースコードです。今回はPythonでコードを書いたのですが、折角ですのでPythonのGUIライブラリであるKivyを使用してアプリ開発しました。
https://github.com/AyatoMatsui/sysken01
パッケージのインストールについてはGitHubに軽く説明を記載しましたが、何か環境構築でエラーが起きた等の話がありましたら相談を受け付けます。Kivyの扱い方については、残念ながら、この場では説明を割愛させていただきます。

アプリの見た目はこんな感じになっております。

本当はモバイル版にデプロイしようと思ったのですが、KivyではAPK出力にBuildozerというツールを要します。そこでパーミッション(storage)の設定をするのが非常に面倒であったため、僕はそこで諦めてしまいました。よって、デスクトップアプリとしてそのまま遊んで貰えたらなと思います。

画面設計の説明としては、まず画面上半分の部分で音声ファイルを選択し、playボタンを押すと、音声が再生されます。テンポ周波数が曲の速さ。±で速さを弄ることができます。そして、隣のクロックボタンを押すと、メトロノームが鳴ります。ただ、これはkivyの仕様のせいでもあるのでしょうか、何故かしらこのメトロノームはテンポがぎこちないですので、正直あまり参考にはならないです。改善点が見つかればプログラムを直しておきます。
最後に、WAV2MIDIというボタンを押せば、MIDI変換は終了です。
残念ながら、MIDI再生機能は用意しておりません。そこも実装できればアップデートしておきたいと思います。

次に、ソースコードの解説をします。
まずはFileChooserIconViewから読み取られた音声ファイル情報をlibrosa.loadで読み込んでいきます。WAV2MIDI変換というボタンは、onClickConvertButtonというメソッドの中で動きます。そこでChroma-CQTをして、そこから最も音圧が大きい音を抽出し、一元配列上に並べていきます。
また、「さしすせそ」や「ちつ」という音は歯擦音と言って、「s」の発音は高周波数帯の音が多く出てしまい、それが音階推定を妨害する羽目になりますので、高い音圧帯は10dB下がるようにカッティングしています。
そのようにして、例えば…
30,30,30,31,31,32
という風に要素が並べば、
[30,3],[31,2],[32,1]
という風に音符の長さを出していきます。
ここまで調理したら、あとはmidoという優秀なPythonのMIDI出力ライブラリがありますので、そこでノートを開いて、順繰りに書き出ししてやって、音声ファイルを選んだディレクトリにそのまま.midを出力してあげれば、念願の歌えば作曲完了システムのできあがりです。

そして、これが音声のサンプルファイルです。Green Sleevesという曲を歌ってます。

…なんか歌う途中で燃え尽きてますが、まぁご容赦を。
これを127.12のテンポで合わせてWAV2MIDI変換を押せば、MIDIファイルが出力されます。
こちらの変換後の.midデータはダウンロード形式になっています。
Record (online-voice-recorder.com).mp3.mid

あとは、これをDAWソフトに向けてドラッグ&ドロップするだけです。それを実際に演奏させてみた結果がこちらになります。

…何というか、残念な感じになってしまいましたが、まぁこれはテンポの調整とか人間の歌の上手い下手にもよると思いますので、微調整はDAWの中で行いましょう。
というわけで、本当にお疲れ様でした。

感想その他

最後に感想と、この記事を書くに至った経緯の話をしたいなと思います。すいません。
今回の制作はとても楽しかったです。テストする方法が、実際に僕が歌ってみるという原始的なやり方だったりしたのも楽しかった点だと思います。
もう今後は作曲も、必死に音感を身に着けたり一生懸命理論を学んだ人たちより、のど自慢の奴や単純にインスピレーションに長けた人たちの方が勝てるって風潮になるのかなという社会現象も気になるところではあります。まぁ最近はボカロPになりたいと言っている子ども達も多いですし、もっとDTMも色んな意味で学習コストが下がってくれると嬉しいですね。

で、ここから先は僕の意見みたいな話になってしまうのですが、すいません。僕がこの記事を書こうと思った理由の話をしていきたいなと思います。
皆さんがこの記事を読んで思ったことは何でしょうか。恐らく、こんな話を聞いても…技術としての応用幅がこれ以外に無いと言いますか、プログラムに関する紹介が薄いと言いますか、まぁ総合的にあまり実用的な話ではなかったように思われた方もいるだろうと思います。今回はそういった方々に向けた話を、少しだけしたいなと思います。
先に簡単な結論を言いますと、今回の記事を書いた趣旨としては、皆さんにアプリ開発の技術を広めるためといった直接的に役に立つ内容ではなく、皆さんに数学の楽しさを教えるためを思って、その事例の一つとしてこれを広めることを考えました。その意図は非常に発展的な話です。
まずアプリ開発において大事なのは、UnityやVue.js等といったツールを使いこなすこと。それは僕もそう思います。
ただ、それで実際に何を作るかというコンセプトを聞かれた時にやるべきことって、フレームワークやその中のデザインツールの使い方やらインフラやらを学ぶことではないと思っていて、そういったアイデアのヒントの一つとして、数学やアルゴリズムがあると思ってます。(勿論、数学に限定されなくとも結構です。)
その比較的身近な例としては、Photoshopの投げ縄ツールとか、Blenderのミラーモディファイアとか。モバイルアプリで使われるAR技術でも、スマホを動かすとちゃんとそのオブジェクトがその定位置に合うよう角度や位置が連動してくれるじゃないですか。あれってどうなってるんだろうとか、実はそういった物事の裏で動作してるものとして、実は案外思ってもない面白い数学が転がってます。
ただ、そういった面白い数学という中でも、今回のChroma-CQTは若干意表を突いた内容だったと思います。その原因としては、そもそも数学に関心のある対象がゲームプログラミングやその他CG系といった視野に限定されているからだと思います。(勿論、そっち系でも面白いアルゴリズムは色々ありますけど。)
その点、今回の音響数理工学は、お世辞にもメジャーとは言えない内容です。しかし、それもよくよく考えてみれば、ITには重要なパーツの一つに組み込めます。そういった視野の拡張こそがアイデアを得る方法の一つだと僕は考えており、実は僕はこれ以外にも何個かそういう系のネタを隠し持ってます。それらを知るアイデアの元となったのは、アプリ開発系のプログラミングとはおおよそ関係のないところですし、何なら数学という論理性からヒントを辿って得たものでもありません。しかし、数学というもの自体には興味を持ってないと、それに辿り着くことは恐らく不可能です。それこそが、応用数学の魅力と言ってもいいところかもしれません。
それに、大学という場所は、勿論製品開発も大事ではあるのですが、どちらかと言えば研究をすることを良しとします。その研究にあたっても、残念ながら、プログラムの書かれてない論文を読むことを求められる場面もしばしばありますので、そういった意味でも、数学に価値を見出すことは何かしらの保険にもなると思います。
まぁそんなわけでダラダラと話してしまいましたが、この文面で何となく数学の重要性が伝わっていただけたでしょうか。
また良ければ何か一つ面白いものでも作って、ここにアイデアを広めたいなと思います。ここまでの記事を読んでくださり、誠にありがとうございました。それでは。

2022年3月10日未分類

Posted by 松井 礼人