private Deemphasis(int length, double sampleRate, double binSize, bool correctPhase, bool doAnalysis) { this.length = length; this.sampleRate = sampleRate; this.binSize = binSize; this.correctPhase = correctPhase; this.filterKernel = null; this.idealFilterResponse = null; this.doAnalysis = doAnalysis; this.actualFilterResponse = null; this.deviation = null; }
//////////////////////////////////////////////////////////////////////////////// // // Function: calculateAmplitudeAndPhase // // Arguments: response: Pointer to the response structure. // R1: The resister 1 value in Ohms. // Rf: The feedback resistor value in Ohms. // C1: The capacitor value in Farads. // // Returns: void // // Description: The function calculates the amplitude response (both linear // and dB) of the ideal de-emphasis filter. It also calculates // the phase response (in radians), phase delay (in seconds), // and group delay (in seconds) of the ideal filter. Note that // this function assumes that the frequency and complex // response vectors have already been calculated. // //////////////////////////////////////////////////////////////////////////////// private static void calculateAmplitudeAndPhase(IdealResponse response, double R1, double Rf, double C1) { // Make sure we have a valid pointer if (response == null) { throw new ArgumentNullException("response"); } // Calculate the magnitude of the frequency response for (int i = 0; i < response.complexResponse.Length; i++) { // Isolate the real and imaginary parts of the complex response double a = response.complexResponse[i].Real; double b = response.complexResponse[i].Imaginary; // Calculate the linear amplitude response.amplitudeLinear[i] = Math.Sqrt((a * a) + (b * b)); // Calculate the dB-scaled amplitude from the linear amplitude response.amplitudeDB[i] = FourierAnalysis.convertToDB(response.amplitudeLinear[i]); // Calculate the phase response (in radians) response.phaseRadians[i] = Math.Atan(b / a); // Calculate the phase delay (in seconds) if (response.frequency[i] == 0.0) { // The phase delay at f = 0 is a special case: it is C1 * Rf response.phaseDelay[i] = C1 * Rf; } else { // The phase delay is phi(f) = - phase(f) / (2 * Pi * f) response.phaseDelay[i] = response.phaseRadians[i] / (-Constants.TAU * response.frequency[i]); } // Calculate the group delay (in seconds) response.groupDelay[i] = groupDelayFunc(response.frequency[i], R1, Rf, C1); } }
//////////////////////////////////////////////////////////////////////////////// // // Function: calculateIdealResponse // // Arguments: length: Filter kernel length. // sampleRate: System sample rate in Hertz. Must // be greater than 0. // // Returns: A structure that holds the ideal frequency response // of the de-emphasis filter. // // Description: This function calculates the ideal frequency response of // the canonical de-emphasis filter needed to correct the pre- // emphasis used on older compact discs (CDs). It does so by // inverting the reponse of the pre-emphasis filter. This // pre-emphasis filter is normally implemented using an op-amp // in a "non-inverting high-pass shelving amplifier" topology. // The input voltage is fed into the +input of the op-amp, // and the output is fed back into the -input through a // feedback resistor (Rf). This input is also grounded through // an R1 resistor and C1 capacitor connected in series. // // |\ // | \ // | \ // Vi ---------|+ \ // | \ // | >------.------- Vo // | / | // .---|- / | // | | / | // | | / | // | |/ | // | Rf | // .------/\/\/-----. // | // \ // / // \ R1 // / // | // = C1 // | // | // GND // // Rf = 35000 Ohms // R1 = 15000 Ohms // C1 = 0.000000001 Farads (1 nF) // //////////////////////////////////////////////////////////////////////////////// public static IdealResponse calculateIdealResponse(int length, double sampleRate) { // Make sure the length of the filter is at least 8 samples if (length < 8) { throw new ArgumentOutOfRangeException("length", "" + length); } // Make sure the length of the filter is a power of 2 if (!Utility.isPowerOf2(length)) { throw new ArgumentOutOfRangeException("length", "" + length); } // Make sure the sample rate is greater than 0 if (sampleRate <= 0) { throw new ArgumentOutOfRangeException("sampleRate", "" + sampleRate); } // Define constants used for the resistors and capacitor in the original // op-amp filter design const double R1 = 15000.0; // Ohms const double Rf = 35000.0; // Ohms const double C1 = 0.000000001; // Farads // Calculate the nyquist frequency double nyquist = sampleRate / 2.0; // Calculate the center index int centerIndex = (length / 2); // Calculate the offset int offset = centerIndex - 1; // Calculate the bin size. This is the difference between the frequencies // of consecutive vector elements. double binSize = nyquist / ((double)centerIndex); // Create a structure to hold the desired frequency response IdealResponse response = new IdealResponse(length); // Set the sample rate and bin size response.sampleRate = sampleRate; response.binSize = binSize; // Calculate the complex frequency response in rectangular form for (int i = 0; i < length; i++) { // Calculate the frequency in Hz double f = sampleRate * ((double)(i - offset) / (double)length); // Record this in the frequency vector response.frequency[i] = f; // Calculate the complex response of the ideal de-emphasis filter // at this frequency response.complexResponse[i] = calculateComplexResponse(f, R1, Rf, C1); } // Calculate the amplitude and phase responses from the complex response calculateAmplitudeAndPhase(response, R1, Rf, C1); // Return the filled-in structure return(response); }
//////////////////////////////////////////////////////////////////////////////// // // Function: createDeemphasisFilter // // Arguments: length: Filter kernel length. // sampleRate: System sample rate in Hertz. Must // be greater than 0. // doAnalysis: If set to true, this function will // calculate and print the filter response. // correctPhase: If set to true, this function corrects the // phase distortion produced by the pre- // emphasis filter. // // Returns: Returns a pointer to a struct containing pointer to the // filter kernel plus other information // // Description: This function creates a filter kernel that implements // the de-emphasis filter needed to compensate for the pre- // emphasis filtering that is present on some CD recordings. // The filter will correct the frequency response of the input // to produce an output that should have a spectrum identical // to the original recording before an emphasis filter was // applied. If the correctPhase switch is set to true, then // the phase is also corrected; if not, a linear phase filter // is created. // //////////////////////////////////////////////////////////////////////////////// public static Deemphasis createDeemphasisFilter(int length, double sampleRate, bool correctPhase, bool doAnalysis) { // Make sure the length of the filter is at least 8 samples if (length < 8) { throw new ArgumentOutOfRangeException("length", "" + length); } // Make sure the length of the filter is power of 2 if (!Utility.isPowerOf2(length)) { throw new ArgumentOutOfRangeException("length", "" + length); } // Make sure the sample rate is greater than 0 if (sampleRate <= 0) { throw new ArgumentOutOfRangeException("sampleRate", "" + sampleRate); } // Calculate the bin size double binSize = sampleRate / (double)length; // Make sure the binsize is > 0 and <= 1/4 of the sample rate. This // guarantees at least 3 data points when calculating the filter // response, i.e. at DC, nyquist/2, and nyquist. if ((binSize <= 0) || (binSize > sampleRate / 4)) { throw new ArgumentOutOfRangeException("binSize", "" + binSize); } // Allocate memory for the deemphasis filter struct Deemphasis deemphasis = new Deemphasis(length, sampleRate, binSize, correctPhase, doAnalysis); // Calculate the ideal frequency response of the de-emphasis filter deemphasis.idealFilterResponse = IdealResponse.calculateIdealResponse(length, sampleRate); // Create the de-emphasis filter kernel, using the ideal filter response deemphasis.filterKernel = createFilterKernel(deemphasis.idealFilterResponse, deemphasis.correctPhase); // If we are doing analysis, then calculate the response and deviation // of this filter kernel, and print out the results if (deemphasis.doAnalysis) { // Calculate the actual filter response //deemphasis.actualFilterResponse = // IdealResponse.calculateIdealResponse(deemphasis.filterKernel, // sampleRate, binSize); // Calculate and print out deviations for // amplitude (dB), phase Delay (s), group delay (s) //deemphasis.deviation = Deviation.calculateDeviation(deemphasis); } // Return a pointer to the newly created deemphasis struct return(deemphasis); }
//////////////////////////////////////////////////////////////////////////////// // // Function: createFilterKernel // // Arguments: idealResponse: Pointer to the ideal filter response. // // Returns: A pointer to the newly-created filter kernel. // // Description: This function creates a real-valued filter kernel whose // response closely matches the response of the ideal // de-emphasis filter, including phase correction. // //////////////////////////////////////////////////////////////////////////////// private static double[] createFilterKernel(IdealResponse idealResponse, bool correctPhase) { int i, j; double a, b, magnitude; // Make sure we have a valid pointer if (idealResponse == null) { throw new ArgumentNullException("idealResponse"); } // Create a complex vector to hold the rotated complex filter response Complex[] complexFilterKernel = new Complex[idealResponse.complexResponse.Length]; // Calculate the number of (positive or negative) harmonics. Since the // complex response contains both positive and negative frequencies, this // will be 1/2 its length. int numberHarmonics = complexFilterKernel.Length / 2; // Calculate the position of the DC component int DCIndex = numberHarmonics - 1; // Rotate and copy the complex response into the vector. We rotate so that // the harmonics are ordered as follows: // 0 (DC), 1, 2, 3, ..., N-1, N (nyquist), -(N-1), ..., -3, -2, -1 // First copy from the harmonics ranging from 0 (DC) to N (nyquist) // into the vector for (i = 0, j = DCIndex; j < complexFilterKernel.Length; i++, j++) { complexFilterKernel[i] = idealResponse.complexResponse[j]; } // Make note of the position of the sample at the Nyquist frequency int nyquistPosition = i - 1; // And then copy the harmonics ranging from -(N-1) to -1 into the vector. // Note that we assume the value of i is carried over from previous loop. for (j = 0; j < DCIndex; i++, j++) { complexFilterKernel[i] = idealResponse.complexResponse[j]; } // If we are NOT doing phase correction, then we are creating a filter // with linear phase. We must use the magnitude for the real part of the // complex response, and set the imaginary part to zero. if (correctPhase == false) { for (i = 0; i < complexFilterKernel.Length; i++) { a = complexFilterKernel[i].Real; b = complexFilterKernel[i].Imaginary; magnitude = Math.Sqrt((a * a) + (b * b)); complexFilterKernel[i] = magnitude + Complex.ImaginaryOne * 0; } } // Correct the complex filter kernel by setting the phase at the nyquist // frequency to zero. The imaginary part is zeroed, and the real part // is given the full magnitude of the original complex value. a = complexFilterKernel[nyquistPosition].Real; b = complexFilterKernel[nyquistPosition].Imaginary; magnitude = Math.Sqrt((a * a) + (b * b)); complexFilterKernel[nyquistPosition] = magnitude + Complex.ImaginaryOne * 0; // Perform a complex scaled in-place IFFT on the complex response. The // result is a complex vector, where all the imaginary components should be // 0.0. Note that the IFFT gives imaginary components very close to 0.0, // while the IDFT gives a noisier result. FourierAnalysis.complexScaledIFFT(complexFilterKernel); // Copy the real part of the complex vector into a newly-created real vector double[] temp = new double[complexFilterKernel.Length]; for (i = 0; i < temp.Length; i++) { temp[i] = complexFilterKernel[i].Real; } // Rotate the vector by N/2 to create the unwindowed filter kernel double[] kernel = new double[temp.Length]; int midpoint = temp.Length / 2; for (i = 0, j = midpoint; j < temp.Length; i++, j++) { kernel[i] = temp[j]; } for (j = 0; j < midpoint; i++, j++) { kernel[i] = temp[j]; } // Apply the Blackman-Harris window to the kernel, to reduce aliasing // which shows up as ripples in the filter's response applyHarrisWindow(kernel); // Return a pointer to the newly-calculated filter kernel return(kernel); }