Пример #1
0
        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);
        }
Пример #2
0
        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);
        }
Пример #3
0
        // この関数は音量制限を行わない。呼び出し側で必要に応じて音量を制限する。
        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);
        }
Пример #4
0
        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);
        }
Пример #5
0
        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);
        }
Пример #6
0
        /// <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 = "";
        }
Пример #8
0
        /// <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);
        }