動機
昔から2年に1回くらいギターをやろうと思って練習を始めては1か月くらいでフェードアウトを繰り返してる。今度こそは継続しようと思うんだけど、他に気になることが出てくるとそっちに集中しちゃって。。。
そんなこんなだけど、コードを知りたいときにはあっきーさんのWaveToneやYAMAHAのChord Trackerを使ってる。どっちも良くできてて、特にWaveToneは解析したデータを可視化してくれたり、キー、テンポ、コードの解析条件を変更できたり、ミキサーの機能等あると便利な機能が充実しているのでとても便利。一方Chord Trackerは全自動で結果の表示と再生のみでユーザが解析に関与できる余地が無いが、少しコード解析結果が良い傾向にある。
WaveToneは、解析結果をMIDIで鳴らすことができるので、原曲の再生をオフにしてMIDIだけならして聞くと原曲を想像することが難しい音が再生される。これではコードを推測することも難しいんじゃないか?とか使い方が悪いのかな?とか思ったり。
音楽素人の自分が耳コピの方法を調べてみて、コード解析に必要なことってこんな感じかなと。
・BPM(小節区切り、音符の長さ)を得る
・鳴っている音からコードを得る
・鳴っている音、キー、前のコードから音楽理論的にコード推測する
多分3つ目は、様々な理由で必ずしもコードの構成音全てを認識することができるとは限らないと思われる。
他の音に埋もれて聞き取れない(聞き取り辛い)とか、そもそも演奏で鳴らしてないとか。
自分のように耳コピしようにもコードを聞き取れないという悩みを持つ人は多いみたいで、ググるとそんな人に対するアドバイスとして良く出てくるのが、ダイアトニックコードや五度圏等の理論に基づいた説明。
これが4つ目で、言っていることは何となくわかる(気がする)。けど、コードも確定した状態で後付けで理論を使った説明はできても理論からコードを確定できるようになるのはなかなか難しそうな気もしてる。
なんで難しいと思うかというと、理論は幅広い上、解釈の仕方も色々あるようなので、そのすべてをロジックとして実装する(身に着ける)ことはかなり困難な気が。特に理論の素人が調べながらでは全部を知るまでに途方もなく時間がかかる気がするから。
多分、教えてくれる人たちは、長く音楽と付き合っていく中で会得した多くの引き出しがあって、聞いてる人の状況に合わせて一部の引き出しで説明してくれてるんだと思う。教えてくれる人達にも得意分野などがあって、全ての人が全ての楽曲をやっつけられるわけではないのかも知れない。
まあ、無理だと思いながらも、気になってしまったので実際に何が難しいのか試してみようと思う。
数学出来ない、音楽理論知らない、楽器引けない状態からでどうなるのか。
音符の認識方法
音声解析に使うFFTの基本
FFTの結果(出力)ってどんなデータになってるのか?
やってみる
private float[,] FFT_HammingWindow_ver0(string fileName) { int frame = 0; System.Text.Encoding enc = new System.Text.UTF8Encoding(false); StreamWriter writer = new StreamWriter(@"log.txt", false, enc); int samplesRead; AudioFileReader audioStream = new AudioFileReader(fileName); // コンストラクタを呼んだ際に、Positionが最後尾に移動したため、0に戻す audioStream.Position = 0; // 波形データを配列samplesに格納 // ステレオの音声ならば、偶数番目が左のデータで奇数番目が右となる float[] samples = new float[audioStream.Length / audioStream.BlockAlign * audioStream.WaveFormat.Channels]; //1サンプルのデータ数 int fftLength = 256; double df = (double)(44100.0 / fftLength); writer.WriteLine(fileName); writer.WriteLine("fs=441000hz、df(周波数分解能)="+df+ "、BL(サンプリング点数)=" + fftLength + "、D(時間窓長)="+ (double)(fftLength / 44100.0)*1000+"ms"); //1サンプルごとに実行するためのイテレータ用変数 int fftPos = 0; // フーリエ変換後の音楽データを格納する配列 (標本化定理より、半分は冗長) // 1次元目はFFTに掛けたフレームの番号、2次元目はそのフレームのFFT結果 float[,] result = new float[samples.Length / fftLength, fftLength / 2]; // 入力ファイルのデータを全て読み込む samplesRead = audioStream.Read(samples, 0, samples.Length); // 波形データにハミング窓をかけたデータを格納する配列 // データにハミング窓をかけてからFFTに渡す。 FFTへの入力データは複素数なので、虚部を0にした複素数に変換する。 Complex[] buffer = new Complex[fftLength]; for (int i = 0; i < samples.Length; i++) { // ハミング窓をかける buffer[fftPos].X = (float)(samples[i] * FastFourierTransform.HammingWindow(fftPos, fftLength)); buffer[fftPos].Y = 0.0f; fftPos++; // 1サンプル分のデータが溜まったとき if (fftLength <= fftPos) { fftPos = 0; // サンプル数の対数をとる (高速フーリエ変換に使用) int m = (int)Math.Log(fftLength, 2.0); // 高速フーリエ変換 FastFourierTransform.FFT(true, m, buffer); for (int k = 0; k < result.GetLength(1); k++) { // 複素数の大きさを計算 double diagonal = Math.Sqrt(buffer[k].X * buffer[k].X + buffer[k].Y * buffer[k].Y); // デシベルの値を計算 double intensityDB = 10.0 * Math.Log10(diagonal); const double minDB = -60.0; // 音の大きさを百分率に変換 double percent = (intensityDB < minDB) ? 1.0 : intensityDB / minDB; // 結果を代入 result[i / fftLength, k] = (float)diagonal; } if (frame < 1) { for (int l = 0; l < result.GetLength(1); l = l + 1) writer.WriteLine(result[frame, l]); // writer.WriteLine(l * df * 2 + "," + result[frame, l]); } writer.WriteLine("frame=" + frame + "--------------------------------------------------"); Console.WriteLine("frame=" + frame + "--------------------------------------------------"); frame++; } } writer.Close(); return result; }