/// <summary> /// One dimensional Discrete Fourier Transform. /// </summary> /// /// <param name="data">Data to transform.</param> /// <param name="direction">Transformation direction.</param> /// public void DFT(ComplexNumber[] data, Direction direction) { int n = data.Length; double arg, cos, sin; ComplexNumber[] dst = new ComplexNumber[n]; // for each destination element for (int i = 0; i < n; i++) { dst[i] = ComplexNumberConstants.Zero; arg = -(int)direction * 2.0 * System.Math.PI * (double)i / (double)n; // sum source elements for (int j = 0; j < n; j++) { cos = System.Math.Cos(j * arg); sin = System.Math.Sin(j * arg); dst[i].Re += (data[j].Re * cos - data[j].Im * sin); dst[i].Im += (data[j].Re * sin + data[j].Im * cos); } } // copy elements if (direction == Direction.Forward) { // devide also for forward transform for (int i = 0; i < n; i++) { data[i].Re = dst[i].Re / n; data[i].Im = dst[i].Im / n; } } else { for (int i = 0; i < n; i++) { data[i].Re = dst[i].Re; data[i].Im = dst[i].Im; } } }
/// <summary> /// Initializes a new instance of the <see cref="Complex"/> class. /// </summary> /// /// <param name="c">Source complex number.</param> /// public ComplexNumber( ComplexNumber c ) { this.Re = c.Re; this.Im = c.Im; }
private void save(ComplexNumber[] data) { try { //byte[] fileData = new byte[data.Length]; SaveFileDialog save = new SaveFileDialog(); save.DefaultExt = ".txt"; bool? saveResult = save.ShowDialog(); if (saveResult == true) { Stream fs = save.OpenFile(); StreamWriter sw = new StreamWriter(fs); for (int i = 0; i < data.Length; i++) { sw.WriteLine(data[i].SquaredMagnitude.ToString()); } sw.Flush(); sw.Close(); } } catch (Exception e) { } }
/*Documentation * Method: manualFrequency * Author: Josh Weese * Purpose: Calculates the freqeuncy based on user input * Parameters: * Returns: * Notes: * #This method is ment for accuracy testing * #creates a sine wave based on the input frequency and sample count * #converts to complex numbers * #runs fft and calculates pitch * ChangeLog: Version 1.0...5/3/2011--Documented */ private void manualFrequency(double freq, int sampleCount, int sampleRate) { //create sine wave double increment = (double)(2 * Math.PI) * freq / sampleRate; double angle = 0; double[] samples = new double[sampleCount]; for (int i = 0; i < samples.Length; i++) { samples[i] = ((double)Math.Sin(angle)); angle += increment; } if ((bool)saveCheck.IsChecked) { save(samples); } //convert to complex numbers ComplexNumber[] complexData1 = new ComplexNumber[sampleCount]; int y = 0; while (y < sampleCount) { complexData1[y] = new ComplexNumber(samples[y]); y++; } //run fft ComplexNumber[] fft1 = ft.FFT(complexData1, FourierTransform.Direction.Forward); if ((bool)saveCheck.IsChecked) { save(fft1); } //frequency fundFreq.fundamentalFreq = frequency.calcFundamentalFreq(fft1, sampleRate); //get pitch pitch = frequency.getPitch(fundFreq.fundamentalFreq, pitchList); //cents fundFreq.cents = frequency.calculateCents(fundFreq.fundamentalFreq, pitch.fundamentalFreq); //actual note fundFreq.note = pitch.note; //accidental fundFreq.accidental = pitch.accidental; }
/*Documentation * Method: read * Author: Josh Weese * Purpose: Reads and processes audio capture memory stream * Parameters: * Returns: * Notes: #reads audio capture memory stream * #converts stream into usable data * #saves data to txt file if option is checked * #chunks data, converts it to complex numbers, runs FFT, and determines pitch and frequency * ChangeLog: Version 1.0...5/3/2011--Documented */ public void read() { //make sure data reached the fft lenght thresh hold if (audioSink.AudioData.Length >= Constants.fftLength) { //read the entire memory stream audioSink.AudioData.Seek(seekPosition, SeekOrigin.Begin); byte[] data = new byte[audioSink.AudioData.Length]; int chunkSize = 4096; int numBytesToRead = (int)audioSink.AudioData.Length; int numBytesRead = 0; while (numBytesToRead > 0) { if (chunkSize + numBytesRead > data.Length) { chunkSize = data.Length - numBytesRead; } int n = audioSink.AudioData.Read(data, numBytesRead, chunkSize); if (n == 0) { break; } numBytesRead += n; numBytesToRead -= n; } //kill the buffer once read audioSink.AudioData.Flush(); audioSink.AudioData.Dispose(); //re-initialize audioSink.AudioData = new MemoryStream(); if (saveFile) { save(data); } //length of data int length; Int16[] int16Data; int y = 0; int z = 0; //convert raw data to useable numbers if (audioSink.AudioFormat.BitsPerSample == 16) { length = data.Length / 2; int16Data = new Int16[length]; if (BitConverter.IsLittleEndian) { Array.Reverse(data); } for (int i = 0; i < length; i += 2) { if (!(i > data.Length)) { int16Data[z] = BitConverter.ToInt16(data, i); } else { int16Data[z] = 0; } z++; } } else { length = data.Length; int16Data = new Int16[length]; for (int i = 0; i < length; i++) { int16Data[i] = data[i]; } } if (saveFile) { save(int16Data); } z = 0; //chunk audio data to feed to FFT and calculate freqency while (true) { //chunk int max; if (length > Constants.fftLength) { max = Constants.fftLength; } else { if (IsPowerOfTwo((ulong)length)) { max = length; } else { break; } } //convert to complex numbers ComplexNumber[] complexData1 = new ComplexNumber[max]; y = 0; while (y < max) { complexData1[y] = new ComplexNumber(int16Data[z] / Constants.downsample); y++; z++; } //run fft ComplexNumber[] fft1 = ft.FFT(complexData1, FourierTransform.Direction.Forward); if (saveFile) { save(fft1); } //retrieve pitch pitch = frequency.getPitch(fundFreq.fundamentalFreq, pitchList); //frequency fundFreq.fundamentalFreq = frequency.calcFundamentalFreq(fft1, audioSink.AudioFormat.SamplesPerSecond); //cents fundFreq.cents = frequency.calculateCents(fundFreq.fundamentalFreq, pitch.fundamentalFreq); //actual note fundFreq.note = pitch.note; //accidental fundFreq.accidental = pitch.accidental; if ((length -= max) == 0) { break; } } } }
/*Documentation * Method: calcFundamentalFreq * Author: Josh Weese and http://www.codeproject.com/KB/audio-video/FftGuitarTuner.aspx * Purpose: Calculates fundamental frequency * Parameters: #ComplexNumber[] fftData...complex number array, the result from the fft * #int samplesPerSec...sample rate * Returns: the fundamental frequency as a double...this is in Hz * Notes: #detects peaks in fft data * #determines exact bin the fundamental lies in * #returns fundamental frequency * ChangeLog: Version 1.0...5/3/2011--Documented */ public double calcFundamentalFreq(ComplexNumber[] fftData, int samplesPerSec) { //array to store fft data double[] spectrogram = new double[fftData.Length]; for (int i = 0; i < spectrogram.Length; i++) { //the squared magnitude represents the spectogram (strength of the audio signal) spectrogram[i] = fftData[i].SquaredMagnitude; } int usefullMinSpectrogram = Math.Max(0, (Constants.minFreq * spectrogram.Length / samplesPerSec)); int usefullMaxSpectrogram = Math.Min(spectrogram.Length, (Constants.maxFreq * fftData.Length / samplesPerSec) + 1); int[] peakIndices = findPeaks(spectrogram, usefullMinSpectrogram, usefullMaxSpectrogram - usefullMinSpectrogram, Constants.peaksCount); if (Array.IndexOf(peakIndices, usefullMinSpectrogram) >= 0) { // lowest usefull frequency bin shows active // looks like is no detectable sound, return 0 return 0; } //Quinn's interpolation to guess exact bin of the fundamental frequency double maximizerRatio1 = 0; double maximizerRatio2 = 0; double quinnEstimator = 0; double quinnEstimator1 = 0; double quinnEstimator2 = 0; maximizerRatio1 = fftData[peakIndices[0] - 1].Magnitude / fftData[peakIndices[0]].Magnitude; maximizerRatio2 = fftData[peakIndices[0] + 1].Magnitude / fftData[peakIndices[0]].Magnitude; quinnEstimator1 = maximizerRatio1 / (1 - maximizerRatio1); quinnEstimator2 = -maximizerRatio2 / (1 - maximizerRatio2); if (quinnEstimator1 > 0 && quinnEstimator2 > 0) { quinnEstimator = quinnEstimator1; } else { quinnEstimator = quinnEstimator2; } //return the fundamental frequency in hz return ((quinnEstimator + peakIndices[0]) / (double)fftData.Length * samplesPerSec); }
// Reorder data for FFT using private static void ReorderData(ComplexNumber[] data) { int len = data.Length; // check data length if ((len < minLength) || (len > maxLength) || (!Tools.IsPowerOf2(len))) throw new ArgumentException("Incorrect data length."); int[] rBits = GetReversedBits(Tools.Log2(len)); for (int i = 0; i < len; i++) { int s = rBits[i]; if (s > i) { ComplexNumber t = data[i]; data[i] = data[s]; data[s] = t; } } }
// Get rotation of complex number private static ComplexNumber[] GetComplexRotation(int numberOfBits, Direction direction) { int directionIndex = (direction == Direction.Forward) ? 0 : 1; // check if the array is already calculated if (complexRotation[numberOfBits - 1, directionIndex] == null) { int n = 1 << (numberOfBits - 1); double uR = 1.0; double uI = 0.0; double angle = System.Math.PI / n * (int)direction; double wR = System.Math.Cos(angle); double wI = System.Math.Sin(angle); double t; ComplexNumber[] rotation = new ComplexNumber[n]; for (int i = 0; i < n; i++) { rotation[i] = new ComplexNumber(uR, uI); t = uR * wI + uI * wR; uR = uR * wR - uI * wI; uI = t; } complexRotation[numberOfBits - 1, directionIndex] = rotation; } return complexRotation[numberOfBits - 1, directionIndex]; }
/// <summary> /// Two dimensional Fast Fourier Transform. /// </summary> /// /// <param name="data">Data to transform.</param> /// <param name="direction">Transformation direction.</param> /// /// <remarks><para><note>The method accepts <paramref name="data"/> array of 2<sup>n</sup> size /// only in each dimension, where <b>n</b> may vary in the [1, 14] range. For example, 16x16 array /// is valid, but 15x15 is not.</note></para></remarks> /// /// <exception cref="ArgumentException">Incorrect data length.</exception> /// public void FFT2(ComplexNumber[,] data, Direction direction) { int k = data.GetLength(0); int n = data.GetLength(1); // check data size if ( (!Tools.IsPowerOf2(k)) || (!Tools.IsPowerOf2(n)) || (k < minLength) || (k > maxLength) || (n < minLength) || (n > maxLength) ) { throw new ArgumentException("Incorrect data length."); } // process rows ComplexNumber[] row = new ComplexNumber[n]; for (int i = 0; i < k; i++) { // copy row for (int j = 0; j < n; j++) row[j] = data[i, j]; // transform it FFT(row, direction); // copy back for (int j = 0; j < n; j++) data[i, j] = row[j]; } // process columns ComplexNumber[] col = new ComplexNumber[k]; for (int j = 0; j < n; j++) { // copy column for (int i = 0; i < k; i++) col[i] = data[i, j]; // transform it FFT(col, direction); // copy back for (int i = 0; i < k; i++) data[i, j] = col[i]; } }
/// <summary> /// One dimensional Fast Fourier Transform. /// </summary> /// /// <param name="data">Data to transform.</param> /// <param name="direction">Transformation direction.</param> /// /// <remarks><para><note>The method accepts <paramref name="data"/> array of 2<sup>n</sup> size /// only, where <b>n</b> may vary in the [1, 14] range.</note></para></remarks> /// /// <exception cref="ArgumentException">Incorrect data length.</exception> /// public ComplexNumber[] FFT(ComplexNumber[] data, Direction direction) { int n = data.Length; int m = Tools.Log2(n); // reorder data first ReorderData(data); // compute FFT int tn = 1, tm; for (int k = 1; k <= m; k++) { ComplexNumber[] rotation = FourierTransform.GetComplexRotation(k, direction); tm = tn; tn <<= 1; for (int i = 0; i < tm; i++) { ComplexNumber t = rotation[i]; for (int even = i; even < n; even += tn) { int odd = even + tm; ComplexNumber ce = data[even]; ComplexNumber co = data[odd]; double tr = co.Re * t.Re - co.Im * t.Im; double ti = co.Re * t.Im + co.Im * t.Re; data[even].Re += tr; data[even].Im += ti; data[odd].Re = ce.Re - tr; data[odd].Im = ce.Im - ti; } } } if (direction == Direction.Forward) { for (int i = 0; i < n; i++) { data[i].Re /= (double)n; data[i].Im /= (double)n; } } return data; }
/// <summary> /// Two dimensional Discrete Fourier Transform. /// </summary> /// /// <param name="data">Data to transform.</param> /// <param name="direction">Transformation direction.</param> /// public void DFT2(ComplexNumber[,] data, Direction direction) { int n = data.GetLength(0); // rows int m = data.GetLength(1); // columns double arg, cos, sin; ComplexNumber[] dst = new ComplexNumber[System.Math.Max(n, m)]; // process rows for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { dst[j] = ComplexNumberConstants.Zero; arg = -(int)direction * 2.0 * System.Math.PI * (double)j / (double)m; // sum source elements for (int k = 0; k < m; k++) { cos = System.Math.Cos(k * arg); sin = System.Math.Sin(k * arg); dst[j].Re += (data[i, k].Re * cos - data[i, k].Im * sin); dst[j].Im += (data[i, k].Re * sin + data[i, k].Im * cos); } } // copy elements if (direction == Direction.Forward) { // devide also for forward transform for (int j = 0; j < m; j++) { data[i, j].Re = dst[j].Re / m; data[i, j].Im = dst[j].Im / m; } } else { for (int j = 0; j < m; j++) { data[i, j].Re = dst[j].Re; data[i, j].Im = dst[j].Im; } } } // process columns for (int j = 0; j < m; j++) { for (int i = 0; i < n; i++) { dst[i] = ComplexNumberConstants.Zero; arg = -(int)direction * 2.0 * System.Math.PI * (double)i / (double)n; // sum source elements for (int k = 0; k < n; k++) { cos = System.Math.Cos(k * arg); sin = System.Math.Sin(k * arg); dst[i].Re += (data[k, j].Re * cos - data[k, j].Im * sin); dst[i].Im += (data[k, j].Re * sin + data[k, j].Im * cos); } } // copy elements if (direction == Direction.Forward) { // devide also for forward transform for (int i = 0; i < n; i++) { data[i, j].Re = dst[i].Re / n; data[i, j].Im = dst[i].Im / n; } } else { for (int i = 0; i < n; i++) { data[i, j].Re = dst[i].Re; data[i, j].Im = dst[i].Im; } } } }