/// <devdoc>
        /// Given the z-plane poles and zeros, compute the rational fraction (the top and bottom polynomial coefficients)
        /// of the filter transfer function in Z.
        /// </devdoc>
        private static Polynomials.RationalFraction ComputeTransferFunction(PolesAndZeros zPlane)
        {
            var topCoeffsComplex = Polynomials.Expand(zPlane.Zeros);
            var bottomCoeffsComplex = Polynomials.Expand(zPlane.Poles);

            // If the GetRealPart() conversion fails because the coffficients are not real numbers, the poles
            // and zeros are not complex conjugates.
            return new Polynomials.RationalFraction
            {
                Top = topCoeffsComplex.Select(x => GetRealPart(x)).ToArray(),
                Bottom = bottomCoeffsComplex.Select(x => GetRealPart(x)).ToArray(),
            };
        }
 /// <devdoc>
 /// Maps the poles and zeros from the s-plane (<c>sPlane)</c>to the z-plane.
 /// </devdoc>
 private static PolesAndZeros MapSPlaneToZPlane(PolesAndZeros sPlane, SToZMappingMethod sToZMappingMethod)
 {
     switch (sToZMappingMethod)
     {
         case SToZMappingMethod.BilinearTransform:
             {
                 return new PolesAndZeros
                 {
                     Poles = DoBilinearTransform(sPlane.Poles),
                     Zeros = Extend(DoBilinearTransform(sPlane.Zeros), sPlane.Poles.Length, new Complex(-1, 0))
                 };
             }
         case SToZMappingMethod.MatchedZTransform:
             {
                 return new PolesAndZeros
                 {
                     Poles = DoMatchedZTransform(sPlane.Poles),
                     Zeros = DoMatchedZTransform(sPlane.Zeros)
                 };
             }
         default:
             throw new System.ComponentModel.InvalidEnumArgumentException("sToZMappingMethod", (int)sToZMappingMethod, typeof(SToZMappingMethod));
     }
 }
        /// <summary>
        /// Transforms the s-plane poles of the prototype filter into the s-plane poles and zeros for a filter
        /// with the specified pass type and cutoff frequencies.
        /// </summary>
        /// <param name="poles">The s-plane poles of the prototype LP filter.</param>
        /// <param name="type">The filter type (supports only low-pass, high-pass, band-pass and band-stop.</param>
        /// <param name="fcf1">
        /// The relative filter cutoff frequency for low-pass/high-pass, lower cutoff frequency for band-pass/band-stop.
        /// The cutoff frequency is specified relative to the sampling rate and must be between 0 and 0.5.
        /// </param>
        /// <param name="fcf2">
        /// Ignored for low-pass/high-pass, the relative upper cutoff frequency for band-pass/band-stop.
        /// The cutoff frequency is specified relative to the sampling rate and must be between 0 and 0.5.
        /// </param>
        /// <param name="preWarp">
        /// <see langword="true"/>true to enable prewarping of the cutoff frequencies (for a later bilinear transform from s-plane to z-plane),
        /// <see langword="false"/> to skip prewarping (for a later matched Z-transform).
        /// </param>
        /// <returns>The s-plane poles and zeros for the specific filter.</returns>
        private static PolesAndZeros Normalize(Complex[] poles, FilterKind type, double fcf1, double fcf2, bool preWarp)
        {
            bool fcf2IsRelevant = type == FilterKind.BandPass || type == FilterKind.BandStop;
            Debug.Assert(fcf1 > 0 && fcf1 < 0.5);
            Debug.Assert(!fcf2IsRelevant || (fcf2 > 0 && fcf2 < 0.5));

            int n = poles.Length;
            double fcf1Warped = Math.Tan(Math.PI * fcf1) / Math.PI;
            double fcf2Warped = fcf2IsRelevant ? Math.Tan(Math.PI * fcf2) / Math.PI : 0;
            double w1 = 2 * Math.PI * (preWarp ? fcf1Warped : fcf1);
            double w2 = 2 * Math.PI * (preWarp ? fcf2Warped : fcf2);

            if (type == FilterKind.LowPass)
            {
                return new PolesAndZeros
                {
                    Poles = poles.Select(x => x * w1).ToArray(),
                    Zeros = new Complex[0]
                };
            }

            if (type == FilterKind.HighPass)
            {
                var sPlane = new PolesAndZeros(n, n);

                for (int i = 0; i < n; ++i)
                    sPlane.Poles[i] = w1 / poles[i];

                return sPlane;
            }

            if (type == FilterKind.BandPass)
            {
                double w0 = Math.Sqrt(w1 * w2);
                double bw = w2 - w1;
                var sPlane = new PolesAndZeros(n * 2, n);

                for (int i = 0; i < n; ++i)
                {
                    var hba = poles[i] * (bw / 2);
                    var temp = Complex.Sqrt(1 - ((w0 / hba) * (w0 / hba)));
                    sPlane.Poles[i] = hba * (temp + 1);
                    sPlane.Poles[n + i] = hba * (1 - temp);
                }

                return sPlane;
            }

            if (type == FilterKind.BandStop)
            {
                double w0 = Math.Sqrt(w1 * w2);
                double bw = w2 - w1;
                var sPlane = new PolesAndZeros(n * 2, n * 2);

                for (int i = 0; i < n; ++i)
                {
                    var hba = (bw / 2) / poles[i];
                    var temp = Complex.Sqrt(1 - ((w0 / hba) * (w0 / hba)));
                    sPlane.Poles[i] = hba * (temp + 1);
                    sPlane.Poles[n + i] = hba * (1 - temp);
                }

                for (int i = 0; i < n; i++)
                {
                    sPlane.Zeros[i] = new Complex(0, w0);
                    sPlane.Zeros[n + i] = new Complex(0, -w0);
                }

                return sPlane;
            }

            throw new System.ComponentModel.InvalidEnumArgumentException("type", (int)type, typeof(FilterKind));
        }