private double [] SetupCoeffs(int windowLen, int upsampleFactor) { var window = WWWindowFunc.BlackmanWindow(windowLen); // ループ処理を簡単にするため最初と最後に0を置く。 var coeffs = new double[1 + windowLen + 1]; long center = windowLen / 2; for (long i = 0; i < windowLen / 2 + 1; ++i) { long numerator = i; int denominator = upsampleFactor; int numeratorReminder = (int)(numerator % (denominator * 2)); if (numerator == 0) { coeffs[1 + center + i] = 1.0f; } else if (numerator % denominator == 0) { // sinc(180 deg) == 0, sinc(360 deg) == 0, ... coeffs[1 + center + i] = 0.0f; } else { coeffs[1 + center + i] = Math.Sin(Math.PI * numeratorReminder / denominator) / (Math.PI * numerator / denominator) * window[center + i]; } coeffs[1 + center - i] = coeffs[1 + center + i]; } return(coeffs); }
public void BartlettWindow5Test() { var expected = new double[] { 0, 0.5, 1, 0.5, 0 }; var actual = WWWindowFunc.BartlettWindow(5); var distance = Functions.AverageDistance(expected, actual); Assert.IsTrue(distance < 1e-8); }
// この関数は音量制限を行わない。呼び出し側で必要に応じて音量を制限する。 private bool PhaseRotationDo(FirWorkerArgs argsFWA, PcmData pcmDataIn, out PcmData pcmDataOutput) { PhaseRotationWorkerArgs args = argsFWA as PhaseRotationWorkerArgs; var conv = new WasapiPcmUtil.PcmFormatConverter(pcmDataIn.NumChannels); PcmData pcmDataReal = conv.Convert(pcmDataIn, Wasapi.WasapiCS.SampleFormatType.Sdouble, null); pcmDataOutput = new PcmData(); pcmDataOutput.CopyFrom(pcmDataIn); PcmData pcmDataImaginary = new PcmData(); pcmDataImaginary.CopyFrom(pcmDataReal); var dft = new WWDirectComputeCS.WWDftCpu(); var hilb = WWHilbert.HilbertFirCoeff(args.hilbertFilterType, args.firLength); System.Diagnostics.Debug.Assert(hilb.Length == args.firLength); // 窓関数 double [] window; if (args.windowFunc == WindowFuncType.Blackman) { WWWindowFunc.BlackmanWindow(args.firLength, out window); } else { WWWindowFunc.KaiserWindow(args.firLength, args.kaiserAlpha, out window); } // FIR coeffの個数は、window.Length個。 // ヒルベルト変換パラメータは未来から過去の方向に並んでいるので左右をひっくり返す。 double [] coeff = new double[args.firLength]; for (int i = 0; i < coeff.Length; ++i) { int pos = coeff.Length - i - 1; coeff[i] = hilb[pos] * window[i]; } for (int ch = 0; ch < pcmDataImaginary.NumChannels; ++ch) { var pcm1ch = new double[pcmDataImaginary.NumFrames]; for (long i = 0; i < pcm1ch.Length; ++i) { pcm1ch[i] = pcmDataImaginary.GetSampleValueInDouble(ch, i); } // 少しずつFIRする。 var fir = new WWFirCpu(); fir.Setup(coeff, pcm1ch); const int FIR_SAMPLE = 65536; for (int offs = 0; offs < pcm1ch.Length; offs += FIR_SAMPLE) { int nSample = FIR_SAMPLE; if (pcm1ch.Length < offs + nSample) { nSample = pcm1ch.Length - offs; } var pcmFir = new double[nSample]; fir.Do(offs - window.Length / 2, nSample, pcmFir); // 結果を出力に書き込む。 for (long i = 0; i < pcmFir.Length; ++i) { var re = pcmFir[i]; pcmDataImaginary.SetSampleValueInDouble(ch, i + offs, (float)(re)); } // 進捗Update。 int percentage = (int)( (100L * ch / pcmDataImaginary.NumChannels) + (100L * (offs + 1) / pcm1ch.Length / pcmDataImaginary.NumChannels)); mPRWorker.ReportProgress(percentage); } fir.Unsetup(); } // 音の位相を回転。 for (int ch = 0; ch < pcmDataReal.NumChannels; ++ch) { for (long pos = 0; pos < pcmDataReal.NumFrames; ++pos) { // 解析信号の各サンプル値を極座標表現に変換。オリジナルの長さと位相を得る。 // 長さをそのままに位相を回転し、回転後の実数成分を出力する。 double x = pcmDataReal.GetSampleValueInDouble(ch, pos); double y = pcmDataImaginary.GetSampleValueInDouble(ch, pos); double norm = Math.Sqrt(x * x + y * y); double theta = Math.Atan2(y, x); double re = norm * Math.Cos(theta + args.phaseRadian); pcmDataOutput.SetSampleValueInDouble(ch, pos, re); } } return(true); }
private bool HilbertDo(FirWorkerArgs argsFir, PcmData pcmDataIn, out PcmData pcmDataOutput) { HilbertWorkerArgs args = argsFir as HilbertWorkerArgs; var dft = new WWDirectComputeCS.WWDftCpu(); var hilb = WWHilbert.HilbertFirCoeff(args.hilbertFilterType, args.firLength); System.Diagnostics.Debug.Assert(hilb.Length == args.firLength); // 窓関数 double [] window; if (args.windowFunc == WindowFuncType.Blackman) { WWWindowFunc.BlackmanWindow(args.firLength, out window); } else { WWWindowFunc.KaiserWindow(args.firLength, args.kaiserAlpha, out window); } // FIR coeffの個数は、window.Length個。 // ヒルベルト変換パラメータは未来から過去の方向に並んでいるので左右をひっくり返す。 double [] coeff = new double[args.firLength]; for (int i = 0; i < coeff.Length; ++i) { int pos = coeff.Length - i - 1; coeff[i] = hilb[pos] * window[i]; } /* * for (int i=0; i < coeff.Length; ++i) { * System.Console.WriteLine("coeff {0:D2} {1}", i, coeff[i]); * } * System.Console.WriteLine(""); */ pcmDataOutput = new PcmData(); pcmDataOutput.CopyFrom(pcmDataIn); for (int ch = 0; ch < pcmDataOutput.NumChannels; ++ch) { // 全てのチャンネルでループ。 var pcm1ch = new double[pcmDataOutput.NumFrames]; for (long i = 0; i < pcm1ch.Length; ++i) { pcm1ch[i] = pcmDataOutput.GetSampleValueInDouble(ch, i); } // 少しずつFIRする。 var fir = new WWFirCpu(); fir.Setup(coeff, pcm1ch); const int FIR_SAMPLE = 65536; for (int offs = 0; offs < pcm1ch.Length; offs += FIR_SAMPLE) { int nSample = FIR_SAMPLE; if (pcm1ch.Length < offs + nSample) { nSample = pcm1ch.Length - offs; } var pcmFir = new double[nSample]; fir.Do(offs - window.Length / 2, nSample, pcmFir); // 結果を出力に書き込む。 for (long i = 0; i < pcmFir.Length; ++i) { var re = pcmFir[i]; pcmDataOutput.SetSampleValueInDouble(ch, i + offs, re); } // 進捗Update。 int percentage = (int)( (100L * ch / pcmDataOutput.NumChannels) + (100L * (offs + 1) / pcm1ch.Length / pcmDataOutput.NumChannels)); m_HilbWorker.ReportProgress(percentage); } fir.Unsetup(); } return(true); }
private bool FirDo(FirWorkerArgs args, PcmData pcmDataIn, out PcmData pcmDataOut) { var dft = new WWDirectComputeCS.WWDftCpu(); var from = FreqGraphToIdftInput(pcmDataIn.SampleRate); double [] idftResult; dft.Idft1d(from, out idftResult); // 窓関数の要素数は、IDFT結果の複素数の個数 -1個。 double [] window; if (args.windowFunc == WindowFuncType.Blackman) { WWWindowFunc.BlackmanWindow((idftResult.Length / 2) - 1, out window); } else { WWWindowFunc.KaiserWindow((idftResult.Length / 2) - 1, args.kaiserAlpha, out window); } // FIR coeffの個数は、window.Length個。 double [] coeff; dft.IdftComplexToFirCoeff1d(idftResult, window, out coeff); /* * for (int i=0; i < coeff.Length; ++i) { * System.Console.WriteLine("coeff {0:D2} {1}", i, coeff[i]); * } * System.Console.WriteLine(""); */ pcmDataOut = new PcmData(); pcmDataOut.CopyFrom(pcmDataIn); for (int ch = 0; ch < pcmDataOut.NumChannels; ++ch) { // 全てのチャンネルでループ。 var pcm1ch = new double[pcmDataOut.NumFrames]; for (long i = 0; i < pcm1ch.Length; ++i) { pcm1ch[i] = pcmDataOut.GetSampleValueInDouble(ch, i); } // 少しずつFIRする。 var fir = new WWFirCpu(); fir.Setup(coeff, pcm1ch); const int FIR_SAMPLE = 65536; for (int offs = 0; offs < pcm1ch.Length; offs += FIR_SAMPLE) { int nSample = FIR_SAMPLE; if (pcm1ch.Length < offs + nSample) { nSample = pcm1ch.Length - offs; } var pcmFir = new double[nSample]; fir.Do(offs - window.Length / 2, nSample, pcmFir); // 結果を出力に書き込む。 for (long i = 0; i < pcmFir.Length; ++i) { var re = pcmFir[i]; pcmDataOut.SetSampleValueInDouble(ch, i + offs, re); } // 進捗Update。 int percentage = (int)( (100L * ch / pcmDataOut.NumChannels) + (100L * (offs + 1) / pcm1ch.Length / pcmDataOut.NumChannels)); m_FirWorker.ReportProgress(percentage); } fir.Unsetup(); } return(true); }
/// <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); } }
private void m_AnalyticSignalWorker_DoWork(object sender, DoWorkEventArgs e) { AnalyticSignalWorkerArgs args = e.Argument as AnalyticSignalWorkerArgs; Stopwatch sw = new Stopwatch(); sw.Start(); // pcmファイルを読み込んでサンプル配列pcm1chを作成。 PcmData pcmDataIn = null; try { pcmDataIn = ReadWavFile(args.inputPath); } catch (IOException ex) { e.Result = string.Format("WAVファイル {0} 読み込み失敗\r\n{1}", args.inputPath, ex); } if (null == pcmDataIn) { e.Result = string.Format("WAVファイル {0} 読み込み失敗", args.inputPath); } var formatConv = new WasapiPcmUtil.PcmFormatConverter(pcmDataIn.NumChannels); PcmData pcmDataReal = formatConv.Convert(pcmDataIn, Wasapi.WasapiCS.SampleFormatType.Sdouble, null); PcmData pcmDataImaginary = new PcmData(); pcmDataImaginary.CopyFrom(pcmDataReal); var dft = new WWDirectComputeCS.WWDftCpu(); var hilb = WWHilbert.HilbertFirCoeff(args.hilbertFilterType, args.firLength); System.Diagnostics.Debug.Assert(hilb.Length == args.firLength); // 窓関数 double [] window; if (args.windowFunc == WindowFuncType.Blackman) { WWWindowFunc.BlackmanWindow(args.firLength, out window); } else { WWWindowFunc.KaiserWindow(args.firLength, args.kaiserAlpha, out window); } // FIR coeffの個数は、window.Length個。 // ヒルベルト変換パラメータは未来から過去の方向に並んでいるので左右をひっくり返す。 double [] coeff = new double[args.firLength]; for (int i = 0; i < coeff.Length; ++i) { int pos = coeff.Length - i - 1; coeff[i] = hilb[pos] * window[i]; } for (int ch = 0; ch < pcmDataImaginary.NumChannels; ++ch) { var pcm1ch = new double[pcmDataImaginary.NumFrames]; for (long i = 0; i < pcm1ch.Length; ++i) { pcm1ch[i] = pcmDataImaginary.GetSampleValueInDouble(ch, i); } // 少しずつFIRする。 var fir = new WWFirCpu(); fir.Setup(coeff, pcm1ch); const int FIR_SAMPLE = 65536; for (int offs = 0; offs < pcm1ch.Length; offs += FIR_SAMPLE) { int nSample = FIR_SAMPLE; if (pcm1ch.Length < offs + nSample) { nSample = pcm1ch.Length - offs; } var pcmFir = new double[nSample]; fir.Do(offs - window.Length / 2, nSample, pcmFir); // 結果を出力に書き込む。 for (long i = 0; i < pcmFir.Length; ++i) { var re = pcmFir[i]; pcmDataImaginary.SetSampleValueInDouble(ch, i + offs, (float)(re)); } // 進捗Update。 int percentage = (int)( (100L * ch / pcmDataImaginary.NumChannels) + (100L * (offs + 1) / pcm1ch.Length / pcmDataImaginary.NumChannels)); m_ASWorker.ReportProgress(percentage); } fir.Unsetup(); } // 解析信号を出力。 m_analyticSignalList.Clear(); for (int ch = 0; ch < pcmDataReal.NumChannels; ++ch) { double [] signal = new double[pcmDataImaginary.NumFrames * 2]; for (long pos = 0; pos < pcmDataReal.NumFrames; ++pos) { signal[pos * 2 + 0] = pcmDataReal.GetSampleValueInDouble(ch, pos); signal[pos * 2 + 1] = pcmDataImaginary.GetSampleValueInDouble(ch, pos); } m_analyticSignalList.Add(signal); } sw.Stop(); e.Result = ""; }
/// <summary> /// バターワースフィルターのFFT coeffs /// </summary> /// <param name="filterSlopeDbOct">1次 = 6(dB/oct), 2次 = 12(dB/oct)</param> /// <returns></returns> public static WWComplex[] Design(int sampleRate, double cutoffFrequency, int fftLength, int filterSlopeDbOct) { int filterLenP1 = fftLength / 4; int filterLength = filterLenP1 - 1; var fromF = new WWComplex[filterLenP1]; double orderX2 = 2.0 * (filterSlopeDbOct / 6.0); double cutoffRatio = cutoffFrequency / (sampleRate / 2); // フィルタのF特 fromF[0] = WWComplex.Unity(); for (int i = 1; i <= filterLenP1 / 2; ++i) { double omegaRatio = i * (1.0 / (filterLenP1 / 2)); double v = Math.Sqrt(1.0 / (1.0 + Math.Pow(omegaRatio / cutoffRatio, orderX2))); if (Math.Abs(v) < Math.Pow(0.5, 24)) { v = 0.0; } fromF[i] = new WWComplex(v, 0); } for (int i = 1; i < filterLenP1 / 2; ++i) { fromF[filterLenP1 - i] = fromF[i]; } // IFFTでfromFをfromTに変換 WWComplex[] fromT; { var fft = new WWRadix2Fft(filterLenP1); fromT = fft.ForwardFft(fromF); double compensation = 1.0 / (filterLenP1 * cutoffRatio); for (int i = 0; i < filterLenP1; ++i) { fromT[i] = new WWComplex( fromT[i].real * compensation, fromT[i].imaginary * compensation); } } fromF = null; // fromTの中心がFILTER_LENGTH/2番に来るようにする。 // delayT[0]のデータはfromF[FILTER_LENGTH/2]だが、非対称になるので入れない // このフィルタの遅延はFILTER_LENGTH/2サンプルある var delayT = new WWComplex[filterLenP1]; delayT[0] = WWComplex.Zero(); for (int i = 1; i < filterLenP1 / 2; ++i) { delayT[i] = fromT[i + filterLenP1 / 2]; } for (int i = 0; i < filterLenP1 / 2; ++i) { delayT[i + filterLenP1 / 2] = fromT[i]; } fromT = null; // Kaiser窓をかける var w = WWWindowFunc.KaiserWindow(filterLenP1 + 1, 9.0); for (int i = 0; i < filterLenP1; ++i) { delayT[i] = WWComplex.Mul(delayT[i], w[i]); } var delayTL = new WWComplex[fftLength]; for (int i = 0; i < delayT.Length; ++i) { delayTL[i] = delayT[i]; } for (int i = delayT.Length; i < delayTL.Length; ++i) { delayTL[i] = WWComplex.Zero(); } delayT = null; // できたフィルタをFFTする WWComplex[] delayF; { var fft = new WWRadix2Fft(fftLength); delayF = fft.ForwardFft(delayTL); for (int i = 0; i < fftLength; ++i) { delayF[i] = WWComplex.Mul(delayF[i], cutoffRatio); } } delayTL = null; return(delayF); }