public void ForwardFft(WWComplex[] aFrom, WWComplex[] aTo)
        {
            if (aFrom == null || aFrom.Length != mNumPoints
                    || aTo == null || aTo.Length != mNumPoints) {
                throw new ArgumentException();
            }

            WWComplex [] aTmp0 = new WWComplex[mNumPoints];
            for (int i=0; i < aTmp0.Length; ++i) {
                aTmp0[i] = new WWComplex(aFrom[mBitReversalTable[i]]);
            }
            WWComplex [] aTmp1 = new WWComplex[mNumPoints];
            for (int i=0; i<aTmp1.Length; ++i) {
                aTmp1[i] = new WWComplex();
            }

            WWComplex [][] aTmps = new WWComplex[2][];
            aTmps[0] = aTmp0;
            aTmps[1] = aTmp1;

            for (int i=0; i<mNumStage-1; ++i) {
                FftStageN(i, aTmps[((i&1) == 1) ? 1 : 0], aTmps[((i&1) == 0) ? 1 : 0]);
            }
            FftStageN(mNumStage - 1, aTmps[(((mNumStage - 1) & 1) == 1) ? 1 : 0], aTo);
        }
        public WWRadix2Fft(int numPoints)
        {
            if (!IsPowerOfTwo(numPoints) || numPoints < 2) {
                throw new ArgumentException();
            }
            mNumPoints = numPoints;

            mWn = new WWComplex[mNumPoints];
            for (int i=0; i < mNumPoints; ++i) {
                double angle = -2.0 * Math.PI * i / mNumPoints;
                mWn[i] = new WWComplex(Math.Cos(angle), Math.Sin(angle));
            }

            // mNumStage == log_2(mNumPoints)
            int t = mNumPoints;
            for (int i=0; 0 < t; ++i) {
                t >>= 1;
                mNumStage = i;
            }

            mBitReversalTable = new uint[mNumPoints];
            for (uint i=0; i < mNumPoints; ++i) {
                mBitReversalTable[i] = BitReversal(mNumStage, i);
            }
        }
 public WWComplex Mul(WWComplex rhs)
 {
     #if false
     // straightforward but slow
     double tR = real * rhs.real      - imaginary * rhs.imaginary;
     double tI = real * rhs.imaginary + imaginary * rhs.real;
     real      = tR;
     imaginary = tI;
     #else
     // more efficient way
     double k1 = real          * (rhs.real  + rhs.imaginary);
     double k2 = rhs.imaginary * (real      + imaginary);
     double k3 = rhs.real      * (imaginary - real);
     real      = k1 - k2;
     imaginary = k1 + k3;
     #endif
     return this;
 }
        public void InverseFft(WWComplex[] aFrom, WWComplex[] aTo, double? compensation = null)
        {
            for (int i=0; i < aFrom.LongLength; ++i) {
                aFrom[i].imaginary *= -1.0;
            }

            ForwardFft(aFrom, aTo);

            double c = 1.0 / mNumPoints;
            if (compensation != null) {
                c = (double)compensation;
            }

            for (int i=0; i < aTo.LongLength; ++i) {
                aTo[i].real *= c;
                aTo[i].imaginary *= -1.0 * c;
            }
        }
        private void UpdateMagnitudePhase()
        {
            var timeDomain = new WWComplex[SampleCount()];
            for (int i=0; i < timeDomain.Length; ++i) {
                timeDomain[i] = new WWComplex(mSLArray[i].Value, 0);
            }

            var freqDomain = new WWComplex[SampleCount()];
            for (int i=0; i < freqDomain.Length; ++i) {
                freqDomain[i] = new WWComplex();
            }

            var fft = new WWRadix2Fft(SampleCount());
            fft.ForwardFft(timeDomain, freqDomain);

            UpdateMagnitude(freqDomain);
            UpdatePhase(freqDomain);
        }
        private void UpdateMagnitude(WWComplex [] freqDomain)
        {
            double pointIntervalX = (canvasWaveFormFR.ActualWidth - GRAPH_YAXIS_POS_X) / (SampleCount() / 2 + 1);

            LineSetX1Y1X2Y2(lineFRX,
                GRAPH_YAXIS_POS_X, canvasWaveFormFR.ActualHeight - GRAPH_YAXIS_POS_X,
                canvasWaveFormFR.ActualWidth, canvasWaveFormFR.ActualHeight - GRAPH_YAXIS_POS_X);

            LineSetX1Y1X2Y2(lineFRY,
                    GRAPH_YAXIS_POS_X, 0,
                    GRAPH_YAXIS_POS_X, canvasWaveFormFR.ActualHeight - GRAPH_YAXIS_POS_X);

            LineSetX1Y1X2Y2(lineFRTickPHalf,
                    GRAPH_YAXIS_POS_X - 4, (canvasWaveFormFR.ActualHeight - GRAPH_YAXIS_POS_X) / 2,
                    GRAPH_YAXIS_POS_X,     (canvasWaveFormFR.ActualHeight - GRAPH_YAXIS_POS_X) / 2);

            LineSetX1Y1X2Y2(lineFRTickPMax,
                    GRAPH_YAXIS_POS_X - 4, 0,
                    GRAPH_YAXIS_POS_X,     0);

            LineSetX1Y1X2Y2(lineFRTickXPi,
                    canvasWaveFormFR.ActualWidth - pointIntervalX, canvasWaveFormFR.ActualHeight - GRAPH_YAXIS_POS_X - 4,
                    canvasWaveFormFR.ActualWidth - pointIntervalX, canvasWaveFormFR.ActualHeight - GRAPH_YAXIS_POS_X);

            CanvasSetLeftTop(labelFRPMax, 0, -labelFRPMax.ActualHeight / 2);
            CanvasSetLeftTop(labelFRPHalf, 0, (canvasWaveFormFR.ActualHeight - GRAPH_YAXIS_POS_X) / 2 - labelFRPHalf.ActualHeight/2);
            CanvasSetLeftTop(labelFR0, 0, canvasWaveFormFR.ActualHeight - GRAPH_YAXIS_POS_X-labelFR0.ActualHeight/2);
            CanvasSetLeftTop(labelFRXPi,
                    canvasWaveFormFR.ActualWidth - pointIntervalX - labelFRXPi.ActualWidth / 2, canvasWaveFormFR.ActualHeight - GRAPH_YAXIS_POS_X);

            labelFRPMax.Content  = string.Format("{0}", SampleCount());
            labelFRPHalf.Content = string.Format("{0}", SampleCount()/2);

            foreach (var t in mMagnitudeGraph.points) {
                canvasWaveFormFR.Children.Remove(t.Item1);
                canvasWaveFormFR.Children.Remove(t.Item2);
            }
            mMagnitudeGraph.points.Clear();

            for (int i=0; i < SampleCount()/2+1; ++i) {
                double x = GRAPH_YAXIS_POS_X + pointIntervalX * i;
                double y = canvasWaveFormFR.ActualHeight - GRAPH_YAXIS_POS_X - (canvasWaveFormFR.ActualHeight - GRAPH_YAXIS_POS_X) * freqDomain[i].Magnitude() / SampleCount();

                var el = new Ellipse();
                el.Width = 6;
                el.Height = 6;
                el.Fill = Brushes.Black;
                canvasWaveFormFR.Children.Add(el);
                CanvasSetLeftTop(el, x - 3, y - 3);

                var la = new Label();
                la.Content = string.Format("{0:0.00}", freqDomain[i].Magnitude());
                canvasWaveFormFR.Children.Add(la);
                CanvasSetLeftTop(la, x, y-labelFRPMax.ActualHeight/2);

                mMagnitudeGraph.points.Add(new Tuple<Ellipse, Label>(el, la));
            }
        }
 public void CopyFrom(WWComplex rhs)
 {
     real      = rhs.real;
     imaginary = rhs.imaginary;
 }
 public WWComplex Add(WWComplex rhs)
 {
     real      += rhs.real;
     imaginary += rhs.imaginary;
     return this;
 }
 public WWComplex(WWComplex rhs)
 {
     this.real      = rhs.real;
     this.imaginary = rhs.imaginary;
 }
        private void FftStageN(int stageNr, WWComplex[] x, WWComplex[] y)
        {
            /*
             * stage0: 2つの入力データにバタフライ演算 (n=8の時) 4回 (nRepeat=4, nSubRepeat=2)
             * y[0] = x[0] + w_n^(0*4) * x[1]
             * y[1] = x[0] + w_n^(1*4) * x[1]
             *
             * y[2] = x[2] + w_n^(0*4) * x[3]
             * y[3] = x[2] + w_n^(1*4) * x[3]
             *
             * y[4] = x[4] + w_n^(0*4) * x[5]
             * y[5] = x[4] + w_n^(1*4) * x[5]
             *
             * y[6] = x[6] + w_n^(0*4) * x[7]
             * y[7] = x[6] + w_n^(1*4) * x[7]
             */

            /*
             * stage1: 4つの入力データにバタフライ演算 (n=8の時) 2回 (nRepeat=2, nSubRepeat=4)
             * y[0] = x[0] + w_n^(0*2) * x[2]
             * y[1] = x[1] + w_n^(1*2) * x[3]
             * y[2] = x[0] + w_n^(2*2) * x[2]
             * y[3] = x[1] + w_n^(3*2) * x[3]
             *
             * y[4] = x[4] + w_n^(0*2) * x[6]
             * y[5] = x[5] + w_n^(1*2) * x[7]
             * y[6] = x[4] + w_n^(2*2) * x[6]
             * y[7] = x[5] + w_n^(3*2) * x[7]
             */

            /*
             * stage2: 8つの入力データにバタフライ演算 (n=8の時) 1回 (nRepeat=1, nSubRepeat=8)
             * y[0] = x[0] + w_n^(0*1) * x[4]
             * y[1] = x[1] + w_n^(1*1) * x[5]
             * y[2] = x[2] + w_n^(2*1) * x[6]
             * y[3] = x[3] + w_n^(3*1) * x[7]
             * y[4] = x[0] + w_n^(4*1) * x[4]
             * y[5] = x[1] + w_n^(5*1) * x[5]
             * y[6] = x[2] + w_n^(6*1) * x[6]
             * y[7] = x[3] + w_n^(7*1) * x[7]
             */

            /*
             * stageN:
             */

            int nRepeat    = Pow2(mNumStage - stageNr - 1);
            int nSubRepeat = mNumPoints / nRepeat;
            var t = new WWComplex();

            for (int i=0; i<nRepeat; ++i) {
                int offsBase = i * nSubRepeat;

                bool allZero = true;
                for (int j=0; j < nSubRepeat/2; ++j) {
                    int offs = offsBase + (j % (nSubRepeat/2));
                    if (Double.Epsilon < x[offs].Magnitude()) {
                        allZero = false;
                        break;
                    }
                    if (Double.Epsilon < x[offs + nSubRepeat / 2].Magnitude()) {
                        allZero = false;
                        break;
                    }
                }

                if (allZero) {
                    for (int j=0; j < nSubRepeat / 2; ++j) {
                        y[j + offsBase].Set(0, 0);
                    }
                } else {
                    for (int j=0; j < nSubRepeat; ++j) {
                        int offs = offsBase + (j % (nSubRepeat / 2));
                        y[j + offsBase].CopyFrom(x[offs]);

                        t.CopyFrom(mWn[j * nRepeat]);
                        t.Mul(x[offs + nSubRepeat / 2]);

                        y[j + offsBase].Add(t);
                    }
                }
            }
        }
Example #11
0
 public void CopyFrom(WWComplex rhs)
 {
     real      = rhs.real;
     imaginary = rhs.imaginary;
 }
Example #12
0
 public WWComplex Add(WWComplex rhs)
 {
     real      += rhs.real;
     imaginary += rhs.imaginary;
     return(this);
 }
Example #13
0
 public WWComplex(WWComplex rhs)
 {
     this.real      = rhs.real;
     this.imaginary = rhs.imaginary;
 }
Example #14
0
        private void FftStageN(int stageNr, WWComplex[] x, WWComplex[] y)
        {
            /*
             * stage0: 2つの入力データにバタフライ演算 (n=8の時) 4回 (nRepeat=4, nSubRepeat=2)
             * y[0] = x[0] + w_n^(0*4) * x[1]
             * y[1] = x[0] + w_n^(1*4) * x[1]
             *
             * y[2] = x[2] + w_n^(0*4) * x[3]
             * y[3] = x[2] + w_n^(1*4) * x[3]
             *
             * y[4] = x[4] + w_n^(0*4) * x[5]
             * y[5] = x[4] + w_n^(1*4) * x[5]
             *
             * y[6] = x[6] + w_n^(0*4) * x[7]
             * y[7] = x[6] + w_n^(1*4) * x[7]
             */

            /*
             * stage1: 4つの入力データにバタフライ演算 (n=8の時) 2回 (nRepeat=2, nSubRepeat=4)
             * y[0] = x[0] + w_n^(0*2) * x[2]
             * y[1] = x[1] + w_n^(1*2) * x[3]
             * y[2] = x[0] + w_n^(2*2) * x[2]
             * y[3] = x[1] + w_n^(3*2) * x[3]
             *
             * y[4] = x[4] + w_n^(0*2) * x[6]
             * y[5] = x[5] + w_n^(1*2) * x[7]
             * y[6] = x[4] + w_n^(2*2) * x[6]
             * y[7] = x[5] + w_n^(3*2) * x[7]
             */

            /*
             * stage2: 8つの入力データにバタフライ演算 (n=8の時) 1回 (nRepeat=1, nSubRepeat=8)
             * y[0] = x[0] + w_n^(0*1) * x[4]
             * y[1] = x[1] + w_n^(1*1) * x[5]
             * y[2] = x[2] + w_n^(2*1) * x[6]
             * y[3] = x[3] + w_n^(3*1) * x[7]
             * y[4] = x[0] + w_n^(4*1) * x[4]
             * y[5] = x[1] + w_n^(5*1) * x[5]
             * y[6] = x[2] + w_n^(6*1) * x[6]
             * y[7] = x[3] + w_n^(7*1) * x[7]
             */

            /*
             * stageN:
             */

            int nRepeat    = Pow2(mNumStage - stageNr - 1);
            int nSubRepeat = mNumPoints / nRepeat;
            var t          = new WWComplex();

            for (int i = 0; i < nRepeat; ++i)
            {
                int offsBase = i * nSubRepeat;

                bool allZero = true;
                for (int j = 0; j < nSubRepeat / 2; ++j)
                {
                    int offs = offsBase + (j % (nSubRepeat / 2));
                    if (Double.Epsilon < x[offs].Magnitude())
                    {
                        allZero = false;
                        break;
                    }
                    if (Double.Epsilon < x[offs + nSubRepeat / 2].Magnitude())
                    {
                        allZero = false;
                        break;
                    }
                }

                if (allZero)
                {
                    for (int j = 0; j < nSubRepeat / 2; ++j)
                    {
                        y[j + offsBase].Set(0, 0);
                    }
                }
                else
                {
                    for (int j = 0; j < nSubRepeat; ++j)
                    {
                        int offs = offsBase + (j % (nSubRepeat / 2));
                        y[j + offsBase].CopyFrom(x[offs]);

                        t.CopyFrom(mWn[j * nRepeat]);
                        t.Mul(x[offs + nSubRepeat / 2]);

                        y[j + offsBase].Add(t);
                    }
                }
            }
        }
        private void UpdatePhase(WWComplex[] freqDomain)
        {
            double pointIntervalX = (canvasWaveFormPhase.ActualWidth - GRAPH_YAXIS_POS_X) / (SampleCount() / 2 + 1);

            LineSetX1Y1X2Y2(linePX,
                GRAPH_YAXIS_POS_X,               canvasWaveFormPhase.ActualHeight / 2,
                canvasWaveFormPhase.ActualWidth, canvasWaveFormPhase.ActualHeight / 2);

            LineSetX1Y1X2Y2(linePY,
                    GRAPH_YAXIS_POS_X, 0,
                    GRAPH_YAXIS_POS_X, canvasWaveFormPhase.ActualHeight);

            LineSetX1Y1X2Y2(linePTickPPi,
                    GRAPH_YAXIS_POS_X - 4, 0,
                    GRAPH_YAXIS_POS_X,     0);

            LineSetX1Y1X2Y2(linePTickMPi,
                    GRAPH_YAXIS_POS_X - 4, canvasWaveFormPhase.ActualHeight-1,
                    GRAPH_YAXIS_POS_X,     canvasWaveFormPhase.ActualHeight-1);

            LineSetX1Y1X2Y2(linePTickXPi,
                    canvasWaveFormPhase.ActualWidth - pointIntervalX, canvasWaveFormPhase.ActualHeight / 2 - 4,
                    canvasWaveFormPhase.ActualWidth - pointIntervalX, canvasWaveFormPhase.ActualHeight / 2);

            CanvasSetLeftTop(labelPPPi, 0, 0 - labelPPPi.ActualHeight / 2);
            CanvasSetLeftTop(labelPMPi, 0, canvasWaveFormPhase.ActualHeight - labelPMPi.ActualHeight/2);
            CanvasSetLeftTop(labelP0, 0, canvasWaveFormPhase.ActualHeight / 2 - labelPMPi.ActualHeight / 2);
            CanvasSetLeftTop(labelPXPi,
                    canvasWaveFormPhase.ActualWidth - pointIntervalX - labelPXPi.ActualWidth/2, canvasWaveFormPhase.ActualHeight / 2);

            foreach (var t in mPhaseGraph.points) {
                canvasWaveFormPhase.Children.Remove(t.Item1);
                canvasWaveFormPhase.Children.Remove(t.Item2);
            }
            mPhaseGraph.points.Clear();

            for (int i=0; i < SampleCount() / 2 + 1; ++i) {
                double x = GRAPH_YAXIS_POS_X + pointIntervalX * i;
                double y = canvasWaveFormPhase.ActualHeight/2 - (canvasWaveFormPhase.ActualHeight)/2/Math.PI * freqDomain[i].Phase();

                var el = new Ellipse();
                el.Width = 6;
                el.Height = 6;
                el.Fill = Brushes.Black;
                canvasWaveFormPhase.Children.Add(el);
                CanvasSetLeftTop(el, x - 3, y - 3);

                var la = new Label();
                la.Content = string.Format("{0:0.00}", freqDomain[i].Phase());
                canvasWaveFormPhase.Children.Add(la);
                CanvasSetLeftTop(la, x, y - labelP0.ActualHeight/2);

                mPhaseGraph.points.Add(new Tuple<Ellipse, Label>(el, la));
            }
        }
Example #16
0
        private void UpdateUpsampleGraph()
        {
            LineSetX1Y1X2Y2(lineUSX,
                            0, canvasUpsampleGraph.ActualHeight / 2,
                            canvasUpsampleGraph.ActualWidth, canvasUpsampleGraph.ActualHeight / 2);

            LineSetX1Y1X2Y2(lineUSY,
                            GRAPH_YAXIS_POS_X, 0,
                            GRAPH_YAXIS_POS_X, canvasUpsampleGraph.ActualHeight);

            LineSetX1Y1X2Y2(lineUSTickP1,
                            GRAPH_YAXIS_POS_X - 4, canvasUpsampleGraph.ActualHeight / 4,
                            GRAPH_YAXIS_POS_X, canvasUpsampleGraph.ActualHeight / 4);
            LineSetX1Y1X2Y2(lineUSTickM1,
                            GRAPH_YAXIS_POS_X - 4, canvasUpsampleGraph.ActualHeight * 3 / 4,
                            GRAPH_YAXIS_POS_X, canvasUpsampleGraph.ActualHeight * 3 / 4);

            CanvasSetLeftTop(labelUSP1, 0, canvasUpsampleGraph.ActualHeight / 4 - labelUSP1.ActualHeight / 2);
            CanvasSetLeftTop(labelUS0, 0, canvasUpsampleGraph.ActualHeight / 2 - labelUS0.ActualHeight / 2);
            CanvasSetLeftTop(labelUSM1, 0, canvasUpsampleGraph.ActualHeight * 3 / 4 - labelUSM1.ActualHeight / 2);

            foreach (var t in mUpsampleGraph.points)
            {
                canvasUpsampleGraph.Children.Remove(t.Item1);
                canvasUpsampleGraph.Children.Remove(t.Item2);
                canvasUpsampleGraph.Children.Remove(t.Item3);
            }
            mUpsampleGraph.points.Clear();

            // オリジナルPCMデータtimeDomainOrigをFFTして周波数ドメインデータfreqDomainOrigを得る
            var timeDomainOrig = new WWComplex[SampleCount()];

            for (int i = 0; i < timeDomainOrig.Length; ++i)
            {
                timeDomainOrig[i] = new WWComplex(mSLArray[i].Value, 0);
            }

            var freqDomainOrig = new WWComplex[SampleCount()];

            {
                var fft = new WWRadix2Fft(SampleCount());
                fft.ForwardFft(timeDomainOrig, freqDomainOrig);
            }

            timeDomainOrig = null;

            // 周波数ドメインデータfreqDomainOrigを0で水増ししたデータfreqDomainUpsampledを作って逆FFTする

            int upsampledSampleCount = SampleCount() * UpsampleMultiple();

            var freqDomainUpsampled = new WWComplex[upsampledSampleCount];

            for (int i = 0; i < freqDomainUpsampled.Length; ++i)
            {
                if (i <= freqDomainOrig.Length / 2)
                {
                    freqDomainUpsampled[i].CopyFrom(freqDomainOrig[i]);
                    if (i == freqDomainOrig.Length / 2)
                    {
                        freqDomainUpsampled[i].Mul(0.5);
                    }
                }
                else if (freqDomainUpsampled.Length - freqDomainOrig.Length / 2 <= i)
                {
                    int pos = i + freqDomainOrig.Length - freqDomainUpsampled.Length;
                    freqDomainUpsampled[i].CopyFrom(freqDomainOrig[pos]);
                    if (freqDomainUpsampled.Length - freqDomainOrig.Length / 2 == i)
                    {
                        freqDomainUpsampled[i].Mul(0.5);
                    }
                }
                else
                {
                    // do nothing
                }
            }
            freqDomainOrig = null;

            var timeDomainUpsampled = new WWComplex[upsampledSampleCount];

            {
                var fft = new WWRadix2Fft(upsampledSampleCount);
                fft.InverseFft(freqDomainUpsampled, timeDomainUpsampled, 1.0 / SampleCount());
            }

            freqDomainUpsampled = null;


            // アップサンプルされたPCMデータtimeDomainUpsampledをグラフにプロットする
            double pointIntervalX = (canvasUpsampleGraph.ActualWidth - GRAPH_YAXIS_POS_X) / (upsampledSampleCount + 2 * UpsampleMultiple());

            for (int i = 0; i < upsampledSampleCount; ++i)
            {
                double x = GRAPH_YAXIS_POS_X + pointIntervalX * (i + UpsampleMultiple());
                double y = canvasUpsampleGraph.ActualHeight / 2 - (canvasUpsampleGraph.ActualHeight / 4) * timeDomainUpsampled[i].real;

                var el = new Ellipse();
                el.Width  = 6;
                el.Height = 6;
                el.Fill   = Brushes.Black;
                canvasUpsampleGraph.Children.Add(el);
                CanvasSetLeftTop(el, x - 3, y - 3);

                var la = new Label();
                la.Content = string.Format("{0:0.00}", timeDomainUpsampled[i].real);
                canvasUpsampleGraph.Children.Add(la);
                if (timeDomainUpsampled[i].real >= 0)
                {
                    CanvasSetLeftTop(la, x - 16, y - labelUS0.ActualHeight);
                }
                else
                {
                    CanvasSetLeftTop(la, x - 16, y);
                }

                var li = new Line();
                li.Stroke = Brushes.Black;
                canvasUpsampleGraph.Children.Add(li);
                LineSetX1Y1X2Y2(li,
                                x, y,
                                x, canvasUpsampleGraph.ActualHeight / 2);

                mUpsampleGraph.points.Add(new Tuple <Ellipse, Label, Line>(el, la, li));
            }
        }
        private void UpdateUpsampleGraph()
        {
            LineSetX1Y1X2Y2(lineUSX,
                    0, canvasUpsampleGraph.ActualHeight / 2,
                    canvasUpsampleGraph.ActualWidth, canvasUpsampleGraph.ActualHeight / 2);

            LineSetX1Y1X2Y2(lineUSY,
                    GRAPH_YAXIS_POS_X, 0,
                    GRAPH_YAXIS_POS_X, canvasUpsampleGraph.ActualHeight);

            LineSetX1Y1X2Y2(lineUSTickP1,
                    GRAPH_YAXIS_POS_X - 4, canvasUpsampleGraph.ActualHeight / 4,
                    GRAPH_YAXIS_POS_X, canvasUpsampleGraph.ActualHeight / 4);
            LineSetX1Y1X2Y2(lineUSTickM1,
                    GRAPH_YAXIS_POS_X - 4, canvasUpsampleGraph.ActualHeight * 3 / 4,
                    GRAPH_YAXIS_POS_X, canvasUpsampleGraph.ActualHeight * 3 / 4);

            CanvasSetLeftTop(labelUSP1, 0, canvasUpsampleGraph.ActualHeight / 4 - labelUSP1.ActualHeight / 2);
            CanvasSetLeftTop(labelUS0, 0, canvasUpsampleGraph.ActualHeight / 2 - labelUS0.ActualHeight / 2);
            CanvasSetLeftTop(labelUSM1, 0, canvasUpsampleGraph.ActualHeight * 3 / 4 - labelUSM1.ActualHeight / 2);

            foreach (var t in mUpsampleGraph.points) {
                canvasUpsampleGraph.Children.Remove(t.Item1);
                canvasUpsampleGraph.Children.Remove(t.Item2);
                canvasUpsampleGraph.Children.Remove(t.Item3);
            }
            mUpsampleGraph.points.Clear();

            // オリジナルPCMデータtimeDomainOrigをFFTして周波数ドメインデータfreqDomainOrigを得る
            var timeDomainOrig = new WWComplex[SampleCount()];
            for (int i=0; i < timeDomainOrig.Length; ++i) {
                timeDomainOrig[i] = new WWComplex(mSLArray[i].Value, 0);
            }

            var freqDomainOrig = new WWComplex[SampleCount()];

            {
                var fft = new WWRadix2Fft(SampleCount());
                fft.ForwardFft(timeDomainOrig, freqDomainOrig);
            }

            timeDomainOrig = null;

            // 周波数ドメインデータfreqDomainOrigを0で水増ししたデータfreqDomainUpsampledを作って逆FFTする

            int upsampledSampleCount = SampleCount() * UpsampleMultiple();

            var freqDomainUpsampled = new WWComplex[upsampledSampleCount];
            for (int i=0; i < freqDomainUpsampled.Length; ++i) {
                if (i <= freqDomainOrig.Length / 2) {
                    freqDomainUpsampled[i].CopyFrom(freqDomainOrig[i]);
                    if (i == freqDomainOrig.Length / 2) {
                        freqDomainUpsampled[i].Mul(0.5);
                    }
                } else if (freqDomainUpsampled.Length - freqDomainOrig.Length / 2 <= i) {
                    int pos = i + freqDomainOrig.Length - freqDomainUpsampled.Length;
                    freqDomainUpsampled[i].CopyFrom(freqDomainOrig[pos]);
                    if (freqDomainUpsampled.Length - freqDomainOrig.Length / 2 == i) {
                        freqDomainUpsampled[i].Mul(0.5);
                    }
                } else {
                    // do nothing
                }
            }
            freqDomainOrig = null;

            var timeDomainUpsampled = new WWComplex[upsampledSampleCount];
            {
                var fft = new WWRadix2Fft(upsampledSampleCount);
                fft.InverseFft(freqDomainUpsampled, timeDomainUpsampled, 1.0 / SampleCount());
            }

            freqDomainUpsampled = null;

            // アップサンプルされたPCMデータtimeDomainUpsampledをグラフにプロットする
            double pointIntervalX = (canvasUpsampleGraph.ActualWidth - GRAPH_YAXIS_POS_X) / (upsampledSampleCount + 2 * UpsampleMultiple());

            for (int i=0; i < upsampledSampleCount; ++i) {
                double x = GRAPH_YAXIS_POS_X + pointIntervalX * (i + UpsampleMultiple());
                double y = canvasUpsampleGraph.ActualHeight / 2 - (canvasUpsampleGraph.ActualHeight / 4) * timeDomainUpsampled[i].real;

                var el = new Ellipse();
                el.Width = 6;
                el.Height = 6;
                el.Fill = Brushes.Black;
                canvasUpsampleGraph.Children.Add(el);
                CanvasSetLeftTop(el, x - 3, y - 3);

                var la = new Label();
                la.Content = string.Format("{0:0.00}", timeDomainUpsampled[i].real);
                canvasUpsampleGraph.Children.Add(la);
                if (timeDomainUpsampled[i].real >= 0) {
                    CanvasSetLeftTop(la, x - 16, y - labelUS0.ActualHeight);
                } else {
                    CanvasSetLeftTop(la, x - 16, y);
                }

                var li = new Line();
                li.Stroke = Brushes.Black;
                canvasUpsampleGraph.Children.Add(li);
                LineSetX1Y1X2Y2(li,
                    x, y,
                    x, canvasUpsampleGraph.ActualHeight / 2);

                mUpsampleGraph.points.Add(new Tuple<Ellipse, Label, Line>(el, la, li));
            }
        }
        private void Butterfly(WWComplex vFrom0, WWComplex vFrom1, WWComplex wn, WWComplex[] vTo, int toPos)
        {
            vTo[toPos].CopyFrom(vFrom0);
            WWComplex t = new WWComplex(vFrom1);
            t.Mul(wn);
            vTo[toPos].Mul(t);

            vTo[toPos + 1].CopyFrom(vFrom0);
            t.Mul(-1);
            vTo[toPos + 1].Mul(t);
        }