private string WriteLRC(List <int> predKeys, PitchEnum pitchEnum, string outputPath) { // key推定値が一つもないときエラー。 int keyCount = 0; foreach (var k in predKeys) { if (0 <= k) { ++keyCount; } } if (keyCount == 0) { return("Error: KeyClassifier failed to predict any key on this audio file!\n"); } int lastKey = -1; using (var sw = new StreamWriter(outputPath)) { for (int i = 0; i < predKeys.Count(); ++i) { int key = predKeys[i]; if (pitchEnum == PitchEnum.BaroquePitch) { // コンサートピッチの評価値をバロックピッチに変換する。 key = mKeyClassifier.KeyIdxToBaroquePitch(key); } if (key == lastKey) { continue; } // 値が変化したので書き込む。 if (key < 0) { double timeSec = (double)i * WINDOW_LENGTH / 2 / SAMPLE_RATE; sw.WriteLine("[{0}] -", SecondToTimeStr(timeSec)); } else { // 同じkeyがKEY_COUNTER個連続したら確定するため // KEY_COUNTER-1個遅延して出るので、その分時間を過去にする。 double timeSec = (double)(i - (KEY_COUNTER - 1)) * WINDOW_LENGTH / 2 / SAMPLE_RATE; sw.WriteLine("[{0}]{1}", SecondToTimeStr(timeSec), mKeyClassifier.KeyIdxToStr(key)); } lastKey = key; } } // 成功。 return(""); }
/// <summary> /// 調を調べてLRCファイルを出力する。 /// </summary> /// <returns>エラーの文字列。成功のとき空文字列。</returns> public string Classify(string inputAudioPath, string outputLrcPath, PitchEnum pitchEnum, BackgroundWorker bw) { WWMFReaderCs.WWMFReader.Metadata meta; LargeArray <byte> dataByteAry; int hr = WWMFReaderCs.WWMFReader.ReadHeaderAndData(inputAudioPath, out meta, out dataByteAry); if (hr < 0) { return(string.Format("Error: Read failed {0} {1}\n", hr, inputAudioPath)); } if (meta.sampleRate != SAMPLE_RATE || meta.bitsPerSample != 16 || meta.numChannels != 2) { return(string.Format("Error: File format is not {0}Hz 16bit 2ch. ({1}Hz {2}bit {3}ch) {4}\n", SAMPLE_RATE, meta.sampleRate, meta.bitsPerSample, meta.numChannels, inputAudioPath)); } if (bw != null) { bw.ReportProgress(0, string.Format("Read {0}\nProcessing...\n", inputAudioPath)); } mReportSW.Start(); // ステレオをモノラル float値にする。 var dataF = StereoToMono(dataByteAry); // 鍵盤の周波数binn番号。 var fIdx = new List <int>(); double f = 110.0; // 110Hz, A2 while (f < 1320.0) { fIdx.Add((int)(f * (((double)WINDOW_LENGTH / 2) / (SAMPLE_RATE / 2)))); f *= Math.Pow(2, 1.0 / 12); } // Hann窓 w。 double[] wD = WWWindowFunc.HannWindow(WINDOW_LENGTH); float[] w = Array.ConvertAll(wD, xD => (float)xD); var fft = new WWRadix2FftF(WINDOW_LENGTH); mKeyHistory = new List <int>(); // Keyの推定値predKeysを作成する。-1のとき不明。 var predKeys = new List <int>(); for (long i = 0; i < dataF.LongLength - (WINDOW_LENGTH * TEMPORAL_WIDTH); i += WINDOW_LENGTH / 2) { var x = new List <float>(); // TEMPORAL_WIDTH (=8)個のFFTを実行する。 for (int j = 0; j < TEMPORAL_WIDTH; ++j) { // 窓関数を掛けてFFTし、大きさを取って実数配列にし、鍵盤の周波数binnの値をxに追加。 var v = PrepareSampleDataForFFT(dataF, i + j * WINDOW_LENGTH, w); var vC = WWComplexF.FromRealArray(v); var fC = fft.ForwardFft(vC).ToArray(); var fR = WWComplexF.ToMagnitudeRealArray(fC); foreach (var k in fIdx) { x.Add(fR[k]); } } // keyを推定する。 var keyP = mKeyClassifier.Classify(x.ToArray()); // keyCounter個同じkeyが連続したら確定する。 var key = FilterKey(keyP); predKeys.Add(key); //Console.WriteLine("{0}, {1} {2}", (double)i / SAMPLE_RATE, mKeyClassifier.KeyIdxToStr(keyP), key); if (1000 < mReportSW.ElapsedMilliseconds) { int percentage = (int)(100 * i / dataF.LongLength); if (bw == null) { Console.Write("{0}% \r", percentage); } else { bw.ReportProgress(percentage, ""); } mReportSW.Restart(); } } if (bw == null) { Console.WriteLine(" \r"); } mReportSW.Stop(); { var r = WriteLRC(predKeys, pitchEnum, outputLrcPath); return(r); } }