/* * Bilinear Transform * * b0 + b1*z^-1 + b2*z^-2 * H(z) = ------------------------ * a0 + a1*z^-1 + a2*z^-2 */ /// <summary> /// Overload for the case where the output values aren't needed by the caller. /// Only the driver target data will be updated. /// </summary> /// <param name="driver"></param> /// <param name="frequencyRange"></param> /// <param name="FcLP"></param> /// <param name="FcHP"></param> public static void Calculate(DriverCore.DriverCore driver, double[] frequencyRange, double FcLP, double FcHP) { double splAtFcLP; double phaseAtFcLP; double splAtFcHP; double phaseAtFcHP; double factoredFcLP; double factoredFcHP; Filters.Calculate(driver, frequencyRange , FcLP, out splAtFcLP, out phaseAtFcLP , FcHP, out splAtFcHP, out phaseAtFcHP , out factoredFcLP, out factoredFcHP); }
/// <summary> /// Calculates the response for a theoretical filter. These are used for design acoustic filter targets, not electrical. /// </summary> /// <param name="driver"></param> /// <param name="frequencyRange"></param> /// <param name="phaseAtFc"></param> /// <param name="hpIsInverted"></param> /// <returns>besselFactoredFc, but only if it is for a Bessel.</returns> public static void Calculate(DriverCore.DriverCore driver, double[] frequencyRange , double FcLP, out double splAtFcLP, out double phaseAtFcLP , double FcHP, out double splAtFcHP, out double phaseAtFcHP , out double factoredFcLP, out double factoredFcHP , bool hpIsInverted = false, double offsetMm = 0.0, bool lpIsOffset = false, bool hpIsOffset = false) { double gain = 1; // Epsilon - This allows change in gain later double minInterpFreq = frequencyRange[0]; int rangeUpperLimit = frequencyRange.Count() - 1; // Count of the frequency specified range double maxInterpFreq = frequencyRange[rangeUpperLimit]; // Upper frequency limit of the interpolations to be made double interpFreq; // Used for iterations double[,] interpMagPhaseLP = new double[frequencyRange.Count(), 3]; // LP mag and phase. double[,] interpMagPhaseHP = new double[frequencyRange.Count(), 3]; // HP mag and phase. double[,] interpMagPhase = new double[frequencyRange.Count(), 3]; // Array to hold interp mag and phase. double gainLP = 1.0; double gainHP = 1.0; double gainAtFcLP = 0.0; double gainAtFcHP = 0.0; double phaseLP = 0.0; double phaseHP = 0.0; // These are calculated on each loop, but it was needed to be able to locate the specific set of two // frequencies between which the Fc is located. splAtFcLP = 0.0; splAtFcHP = 0.0; phaseAtFcLP = 0.0; phaseAtFcHP = 0.0; factoredFcLP = 1.0; factoredFcHP = 1.0; for (int i = 0; i < rangeUpperLimit; i++) { interpFreq = frequencyRange[i]; // Interpolation frequency points to use from the range specified. interpMagPhase[i, 0] = interpFreq; interpMagPhase[i, 1] = 1; // SPL default interpMagPhase[i, 2] = 0; // Phase default interpMagPhaseLP[i, 0] = interpMagPhase[i, 0]; interpMagPhaseLP[i, 1] = interpMagPhase[i, 1]; interpMagPhaseLP[i, 2] = interpMagPhase[i, 2]; interpMagPhaseHP[i, 0] = interpMagPhase[i, 0]; interpMagPhaseHP[i, 1] = interpMagPhase[i, 1]; interpMagPhaseHP[i, 2] = interpMagPhase[i, 2]; double s; double besselFactor = 1.0; // Multiply the Fc (divide into s) // Add offset to LP or HP based on user input double offsetDelay = (2 * Math.PI * interpMagPhase[i, 0] * (offsetMm / 344000)); double offsetDelayAtFcLP = (2 * Math.PI * FcLP * (offsetMm / 344000)); double offsetDelayAtFcHP = (2 * Math.PI * FcHP * (offsetMm / 344000)); double offsetLP = 0, offsetHP = 0; double offsetLpAtFc = 0, offsetHpAtFc = 0; if (lpIsOffset) { offsetLP = offsetDelay; offsetLpAtFc = offsetDelayAtFcLP; } if (hpIsOffset) { offsetHP = offsetDelay; offsetHpAtFc = offsetDelayAtFcHP; } driver.target.splInterpolated[i, 1] = 0; driver.target.splInterpolated[i, 2] = 0; if ((driver.target.section == FilterCore.Section.LP) || (driver.target.section == FilterCore.Section.BP)) { s = interpFreq / driver.target.frequencyLP; switch (driver.target.nameLP) { case AcousticTargets.FilterName.Butterworth: { factoredFcLP = driver.target.frequencyLP; gainLP = FilterSPL.ButterworthSPL(driver.target, driver.target.nameLP, driver.target.orderLP, gain, s); phaseLP = FilterPhase.ButterworthPhase(driver.target.orderLP, s) + offsetLP; phaseLP = FilterCore.RotatePhase(phaseLP); phaseAtFcLP = FilterPhase.ButterworthPhase(driver.target.orderLP, s = 1) + offsetLpAtFc; phaseAtFcLP = (FilterCore.RotatePhase(phaseAtFcLP) * 180.0) / Math.PI; gainAtFcLP = FilterSPL.ButterworthSPL(driver.target, driver.target.nameLP, driver.target.orderLP, gain, s = 1); break; } case AcousticTargets.FilterName.LinkwitzRiley: { if (driver.target.fullTypeLP == null) { return; } // Handle odd-order values not permissible for L-R factoredFcLP = driver.target.frequencyLP; gainLP = Math.Pow(FilterSPL.ButterworthSPL(driver.target, driver.target.nameLP, driver.target.orderLP, gain, s), 2); phaseLP = FilterPhase.LinkwitzRileyPhase(driver.target.orderLP, s) + offsetLP; phaseLP = FilterCore.RotatePhase(phaseLP); phaseAtFcLP = FilterPhase.LinkwitzRileyPhase(driver.target.orderLP, s = 1) + offsetLpAtFc; phaseAtFcLP = FilterCore.RotatePhase(phaseAtFcLP) * 180 / Math.PI; gainAtFcLP = Math.Pow(FilterSPL.ButterworthSPL(driver.target, driver.target.nameLP, driver.target.orderLP, gain, s = 1), 2); break; } case AcousticTargets.FilterName.Bessel: { factoredFcLP = driver.target.frequencyLP; gainLP = FilterSPL.BesselSPL(driver.target.orderLP, gain, s); phaseLP = FilterPhase.BesselPhase(driver.target.orderLP, s) + offsetLP; phaseLP = FilterCore.RotatePhase(phaseLP); phaseAtFcLP = FilterPhase.BesselPhase(driver.target.orderLP, s = 1) + offsetLpAtFc; phaseAtFcLP = FilterCore.RotatePhase(phaseAtFcLP) * 180 / Math.PI; gainAtFcLP = FilterSPL.BesselSPL(driver.target.orderLP, gain, s = 1); break; } case AcousticTargets.FilterName.BesselPhaseMatch: { switch (driver.target.orderLP) { case 2: { besselFactor = 0.58; break; } case 3: { besselFactor = 1.0; break; } case 4: { besselFactor = 0.31; break; } } s /= besselFactor; factoredFcLP = driver.target.frequencyLP * besselFactor; gainLP = FilterSPL.BesselSPL(driver.target.orderLP, gain, s); phaseLP = FilterPhase.BesselPhase(driver.target.orderLP, s) + offsetLP; phaseLP = FilterCore.RotatePhase(phaseLP); phaseAtFcLP = FilterPhase.BesselPhase(driver.target.orderLP, s = 1 / besselFactor) + offsetLpAtFc; phaseAtFcLP = FilterCore.RotatePhase(phaseAtFcLP) * 180 / Math.PI; gainAtFcLP = FilterSPL.BesselSPL(driver.target.orderLP, gain, s = 1 / besselFactor); break; } case AcousticTargets.FilterName.BesselFlattest: { switch (driver.target.orderLP) { case 2: { besselFactor = 0.51; break; } case 3: { besselFactor = 1.0; break; } case 4: { besselFactor = 0.40; break; } } s /= besselFactor; factoredFcLP = driver.target.frequencyLP * besselFactor; gainLP = FilterSPL.BesselSPL(driver.target.orderLP, gain, s); phaseLP = FilterPhase.BesselPhase(driver.target.orderLP, s) + offsetLP; phaseLP = FilterCore.RotatePhase(phaseLP); phaseAtFcLP = FilterPhase.BesselPhase(driver.target.orderLP, s = 1 / besselFactor) + offsetLpAtFc; phaseAtFcLP = FilterCore.RotatePhase(phaseAtFcLP) * 180 / Math.PI; gainAtFcLP = FilterSPL.BesselSPL(driver.target.orderLP, gain, s = 1 / besselFactor); break; } case AcousticTargets.FilterName.Bessel3db: { switch (driver.target.orderLP) { case 2: { besselFactor = 0.73; break; } case 3: { besselFactor = 1.0; break; } case 4: { besselFactor = 0.48; break; } } s /= besselFactor; factoredFcLP = driver.target.frequencyLP * besselFactor; gainLP = FilterSPL.BesselSPL(driver.target.orderLP, gain, s); phaseLP = FilterPhase.BesselPhase(driver.target.orderLP, s) + offsetLP; phaseLP = FilterCore.RotatePhase(phaseLP); phaseAtFcLP = FilterPhase.BesselPhase(driver.target.orderLP, s = 1 / besselFactor) + offsetLpAtFc; phaseAtFcLP = FilterCore.RotatePhase(phaseAtFcLP) * 180 / Math.PI; gainAtFcLP = FilterSPL.BesselSPL(driver.target.orderLP, gain, s = 1 / besselFactor); break; } case AcousticTargets.FilterName.None: { break; } default: { MessageBox.Show("Section.LP (.typeLP) is not correct."); break; } } } if ((driver.target.section == FilterCore.Section.HP) || (driver.target.section == FilterCore.Section.BP)) { s = interpFreq / driver.target.frequencyHP; // f/Fc switch (driver.target.nameHP) { case AcousticTargets.FilterName.Butterworth: { factoredFcHP = driver.target.frequencyHP; gainHP = FilterSPL.ButterworthSPL(driver.target, driver.target.nameHP, driver.target.orderHP, gain, 1 / s); phaseHP = FilterPhase.ButterworthPhase(driver.target.orderHP, s, true) + offsetHP; offsetHpAtFc *= factoredFcHP; // Determine the offset using the factored Fc. phaseAtFcHP = FilterPhase.ButterworthPhase(driver.target.orderHP, s = 1, true) + offsetHpAtFc; if (hpIsInverted) { phaseHP = FilterCore.InvertPhase(phaseHP); phaseAtFcHP = FilterCore.InvertPhase(phaseAtFcHP) * 180 / Math.PI; } else { phaseHP = FilterCore.RotatePhase(phaseHP); phaseAtFcHP = FilterCore.RotatePhase(phaseAtFcHP) * 180 / Math.PI; } gainAtFcHP = FilterSPL.ButterworthSPL(driver.target, driver.target.nameHP, driver.target.orderHP, gain, s = 1); break; } case AcousticTargets.FilterName.LinkwitzRiley: { if (driver.target.fullTypeHP == null) { return; } // Handle odd-order values not permissible for L-R factoredFcHP = driver.target.frequencyHP; if (null == driver.target.fullTypeHP) { return; } // Handle odd-order values not permissible for L-R gainHP = Math.Pow(FilterSPL.ButterworthSPL(driver.target, driver.target.nameHP, driver.target.orderHP, gain, 1 / s), 2); phaseHP = FilterPhase.LinkwitzRileyPhase(driver.target.orderHP, s, true) + offsetHP; offsetHpAtFc *= factoredFcHP; // Determine the offset using the factored Fc. phaseAtFcHP = FilterPhase.LinkwitzRileyPhase(driver.target.orderHP, s = 1, true) + offsetHpAtFc; if (hpIsInverted) { phaseHP = FilterCore.InvertPhase(phaseHP); phaseAtFcHP = FilterCore.InvertPhase(phaseAtFcHP) * 180 / Math.PI; } else { phaseHP = FilterCore.RotatePhase(phaseHP); phaseAtFcHP = FilterCore.RotatePhase(phaseAtFcHP) * 180 / Math.PI; } gainAtFcHP = Math.Pow(FilterSPL.ButterworthSPL(driver.target, driver.target.nameHP, driver.target.orderHP, gain, s = 1), 2); break; } case AcousticTargets.FilterName.Bessel: { factoredFcHP = driver.target.frequencyHP; gainHP = FilterSPL.BesselSPL(driver.target.orderHP, gain, 1 / s); phaseHP = FilterPhase.BesselPhase(driver.target.orderHP, s, true) + offsetHP; offsetHpAtFc *= factoredFcHP; // Determine the offset using the factored Fc. phaseAtFcHP = FilterPhase.BesselPhase(driver.target.orderHP, s = 1, true) + offsetHpAtFc; if (hpIsInverted) { phaseHP = FilterCore.InvertPhase(phaseHP); phaseAtFcHP = FilterCore.InvertPhase(phaseAtFcHP) * 180 / Math.PI; } else { phaseHP = FilterCore.RotatePhase(phaseHP); phaseAtFcHP = FilterCore.RotatePhase(phaseAtFcHP) * 180 / Math.PI; } gainAtFcHP = FilterSPL.BesselSPL(driver.target.orderHP, gain, s = 1); break; } case AcousticTargets.FilterName.BesselPhaseMatch: { switch (driver.target.orderHP) { case 2: { besselFactor = 1 / 0.58; break; } case 3: { besselFactor = 1.0; break; } case 4: { besselFactor = 1 / 0.31; break; } } s /= besselFactor; factoredFcHP = driver.target.frequencyHP * besselFactor; gainHP = FilterSPL.BesselSPL(driver.target.orderHP, gain, 1 / s); phaseHP = FilterPhase.BesselPhase(driver.target.orderHP, s, true) + offsetHP; offsetHpAtFc *= factoredFcHP; // Determine the offset using the factored Fc. phaseAtFcHP = FilterPhase.BesselPhase(driver.target.orderHP, s = 1 / besselFactor, true) + offsetHpAtFc; if (hpIsInverted) { phaseHP = FilterCore.InvertPhase(phaseHP); phaseAtFcHP = FilterCore.InvertPhase(phaseAtFcHP) * 180 / Math.PI; } else { phaseHP = FilterCore.RotatePhase(phaseHP); phaseAtFcHP = FilterCore.RotatePhase(phaseAtFcHP) * 180 / Math.PI; } gainAtFcHP = FilterSPL.BesselSPL(driver.target.orderHP, gain, s = 1 * besselFactor); break; } case AcousticTargets.FilterName.BesselFlattest: { switch (driver.target.orderHP) { case 2: { besselFactor = 1 / 0.51; break; } case 3: { besselFactor = 1.0; break; } case 4: { besselFactor = 1 / 0.40; break; } } s /= besselFactor; factoredFcHP = driver.target.frequencyHP * besselFactor; gainHP = FilterSPL.BesselSPL(driver.target.orderHP, gain, 1 / s); phaseHP = FilterPhase.BesselPhase(driver.target.orderHP, s, true) + offsetHP; offsetHpAtFc *= factoredFcHP; // Determine the offset using the factored Fc. phaseAtFcHP = FilterPhase.BesselPhase(driver.target.orderHP, s = 1 / besselFactor, true) + offsetHpAtFc; if (hpIsInverted) { phaseHP = FilterCore.InvertPhase(phaseHP); phaseAtFcHP = FilterCore.InvertPhase(phaseAtFcHP) * 180 / Math.PI; } else { phaseHP = FilterCore.RotatePhase(phaseHP); phaseAtFcHP = FilterCore.RotatePhase(phaseAtFcHP) * 180 / Math.PI; } gainAtFcHP = FilterSPL.BesselSPL(driver.target.orderHP, gain, s = 1 * besselFactor); break; } case AcousticTargets.FilterName.Bessel3db: { switch (driver.target.orderHP) { case 2: { besselFactor = 1 / 0.73; break; } case 3: { besselFactor = 1.0; break; } case 4: { besselFactor = 1 / 0.48; break; } } s /= besselFactor; factoredFcHP = driver.target.frequencyHP * besselFactor; gainHP = FilterSPL.BesselSPL(driver.target.orderHP, gain, 1 / s); phaseHP = FilterPhase.BesselPhase(driver.target.orderHP, s, true) + offsetHP; offsetHpAtFc *= factoredFcHP; // Determine the offset using the factored Fc. phaseAtFcHP = FilterPhase.BesselPhase(driver.target.orderHP, s = 1 / besselFactor, true) + offsetHpAtFc; if (hpIsInverted) { phaseHP = FilterCore.InvertPhase(phaseHP); phaseAtFcHP = FilterCore.InvertPhase(phaseAtFcHP) * 180 / Math.PI; } else { phaseHP = FilterCore.RotatePhase(phaseHP); phaseAtFcHP = FilterCore.RotatePhase(phaseAtFcHP) * 180 / Math.PI; } gainAtFcHP = FilterSPL.BesselSPL(driver.target.orderHP, gain, s = 1 * besselFactor); break; } case AcousticTargets.FilterName.None: { break; } default: { MessageBox.Show("Section.HP (.typeHP) is not correct. Must be null."); break; } } } double factorLP = 0.0; double factorFcLP = 0.0; Complex complexLP = 0.0; double factorHP = 0.0; double factorFcHP = 0.0; Complex complexHP = 0.0; interpMagPhase[i, 0] = interpFreq; switch (driver.target.section) { case FilterCore.Section.LP: { factorLP = 20 * Math.Log10(gainLP); interpMagPhaseLP[i, 1] = driver.target.targetMag + factorLP; // Subtract filter factorLP (in db) from maximum gain (sensitivity) interpMagPhaseLP[i, 2] = phaseLP; complexLP = Complex.FromPolarCoordinates(factorLP, phaseLP); factorFcLP = 20 * Math.Log10(gainAtFcLP); splAtFcLP = driver.target.targetMag + factorFcLP; interpMagPhase[i, 1] = interpMagPhaseLP[i, 1]; interpMagPhase[i, 2] = interpMagPhaseLP[i, 2] * 180 / Math.PI; break; } case FilterCore.Section.HP: { factorHP = 20 * Math.Log10(gainHP); interpMagPhaseHP[i, 1] = driver.target.targetMag + factorHP; // Subtract filter factorHP (in db) from maximum gain (sensitivity) interpMagPhaseHP[i, 2] = phaseHP; complexHP = Complex.FromPolarCoordinates(factorHP, phaseHP); factorFcHP = 20 * Math.Log10(gainAtFcHP); splAtFcHP = driver.target.targetMag + factorFcHP; interpMagPhase[i, 1] = interpMagPhaseHP[i, 1]; interpMagPhase[i, 2] = interpMagPhaseHP[i, 2] * 180 / Math.PI; break; } case FilterCore.Section.BP: { // Update the out arguments factorFcLP = 20 * Math.Log10(gainAtFcLP); factorFcHP = 20 * Math.Log10(gainAtFcHP); splAtFcLP = driver.target.targetMag + factorFcLP; splAtFcHP = driver.target.targetMag + factorFcHP; // Calculate the bandpass //Complex target = Complex.FromPolarCoordinates(driver.target.targetMag, 0.0); Complex complexGainLP = Complex.FromPolarCoordinates(gainLP, phaseLP); Complex complexGainHP = Complex.FromPolarCoordinates(gainHP, phaseHP); Complex complexGainSum = complexGainLP * complexGainHP; double totalGainDb = driver.target.targetMag + (20 * Math.Log10(complexGainSum.Magnitude)); interpMagPhase[i, 1] = totalGainDb; interpMagPhase[i, 2] = complexGainSum.Phase * 180 / Math.PI; break; } } } driver.target.splInterpolated = interpMagPhase; return; }
/// <summary> /// Driver SPL/Phase Curves. /// These were designed because of the desire to have a graph point precisely at the Fc. /// </summary> public static void AddDriverComplexCurves(ZedGraphControl myZGC, DriverCore.DriverCore driver , Color nextColor, string curveName, string primaryTag, string secondaryTag , double FcLP, double splAtFcLP, double phaseAtFcLP , double FcHP, double splAtFcHP, double phaseAtFcHP , bool curveIsVisible = true) { if (null == driver.splInterpolated) { return; } double minInterpFreq = FrequencyRange.dynamicFreqs[0]; PointPairList list1 = new PointPairList(); PointPairList list2 = new PointPairList(); bool magIsVisible = true; double x = new double(); // Holder for frequency data double y = new double(); // Holder for magnitude data double y2 = new double(); // Holder for phase data or group delay data // First remove the old SPL and Phase curves int curveIndex = myZGC.GraphPane.CurveList.IndexOfTag(primaryTag); if (curveIndex != -1) { magIsVisible = myZGC.GraphPane.CurveList[curveIndex].IsVisible; myZGC.GraphPane.CurveList.RemoveAt(curveIndex); } curveIndex = myZGC.GraphPane.CurveList.IndexOfTag(secondaryTag); if (curveIndex != -1) { curveIsVisible = myZGC.GraphPane.CurveList[curveIndex].IsVisible; myZGC.GraphPane.CurveList.RemoveAt(curveIndex); } myZGC.AxisChange(); // Trigger the driver graph update // Build the curve from data, effectively each frequency point. for (int i = 0; i < (FrequencyRange.dynamicFreqs.Count() - 1); i++) { //x = FrequencyRange.dynamicFreqs[i]; // Interpolation frequency point x = driver.splInterpolated[i, 0]; // Interpolation frequency point y = driver.splInterpolated[i, 1]; // Magnitude (SPL or impedance) y2 = driver.splInterpolated[i, 2]; // Phase (SPL or impedance) OR Group Delay // Don't want any points below 10Hz (or whatever the low limit becomes) if (x < minInterpFreq) { continue; } if (y != 0) { list1.Add(x, y); } if (y2 != 0) { list2.Add(x, y2); } if (primaryTag.Contains("Sum")) { continue; } // No filter sections, skip the rest // Add the points precisely at Fc, LP and/or HP (both for BP) if (i >= FrequencyRange.dynamicFreqs.Count() - 1) { continue; } // Don't want to crash on the last pass if ((driver.target.section == FilterCore.Section.LP) || (driver.target.section == FilterCore.Section.BP)) { if ((driver.splInterpolated[i, 0] < FcLP) && (driver.splInterpolated[i + 1, 0] > FcLP)) { list1.Add(FcLP, splAtFcLP); list2.Add(FcLP, phaseAtFcLP); } } if ((driver.target.section == FilterCore.Section.HP) || (driver.target.section == FilterCore.Section.BP)) { if ((driver.splInterpolated[i, 0] < FcHP) && (driver.splInterpolated[i + 1, 0] > FcHP) && !(primaryTag.Contains("Sum"))) { list1.Add(FcHP, splAtFcHP); list2.Add(FcHP, phaseAtFcHP); } } } { // Add the magnitude curve to the specified graph. Color color = nextColor; LineItem curve1 = myZGC.GraphPane.AddCurve(primaryTag, list1, color, SymbolType.None); curve1.Label.Text = curveName; curve1.IsSelectable = true; curve1.Line.Width = 1.6F; curve1.Symbol.Fill = new Fill(Color.White); curve1.Symbol.Size = 5; curve1.Tag = primaryTag; curve1.Line.IsSmooth = true; curve1.Line.IsAntiAlias = true; curve1.Line.SmoothTension = 0.0F; curve1.IsVisible = magIsVisible; curve1.Label.IsVisible = true; // Add the phase curve to the specified graph if optional is present //if (secondaryTag != String.Empty) { LineItem curve2 = myZGC.GraphPane.AddCurve(secondaryTag, list2, color, SymbolType.None); curve2.Label.IsVisible = false; curve2.IsSelectable = true; curve2.IsY2Axis = true; curve2.Line.Width = 1.0F; curve2.Line.IsSmooth = true; curve2.Line.IsAntiAlias = true; curve2.Line.SmoothTension = 0.0F; curve2.Line.Style = DashStyle.Dash; curve2.Symbol.Fill = new Fill(Color.White); curve2.Symbol.Size = 5; curve2.Tag = secondaryTag; //curve2.Line.SmoothTension = 0.1F; curve2.IsVisible = curveIsVisible; curve2.Label.IsVisible = true; } } }