//////////////////////////////////////////////////////////////////////////////// // // 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: 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); }