0
votes

I would like to see how certain frequencies, specifically low bass at 20 - 60hz are present in a piece of audio. I have the audio as a byte array, I convert it to array of shorts, then into a complex number by (short[i]/(double)short.MaxValue, 0). Then i pass this to the FFT from Aforge.

The audio is mono and sample rate of 44100. I understand I can only put chucks through the FFT at ^2. So 4096 for example. I don't understand what frequencies be in the output bins.

if I am taking 4096 samples from the audio that is at 44100 sample rate. Does this mean I am taking milliseconds worth of audio? or only getting some of the frequencies that will be present?

I add the output of the FFT to a array, my understanding is that as I am taking 4096 then bin 0 would contain 0*44100/4096 = 0hz, bin 1 would hold 1*44100/4096 = 10.7666015625hz and so on. Is this correct? or im I doing something fundamentally wrong here?

My goal would be to average the frequencies between say 20 - 60 hz, so for a song with very low, heavy bass then this number would be higher than say a soft piano piece with very little bass.

Here is my code.

OpenFileDialog file = new OpenFileDialog();
file.ShowDialog();
WaveFileReader reader = new WaveFileReader(file.FileName);

byte[] data = new byte[reader.Length];
reader.Read(data, 0, data.Length);

samepleRate = reader.WaveFormat.SampleRate;
bitDepth = reader.WaveFormat.BitsPerSample;
channels = reader.WaveFormat.Channels;

Console.WriteLine("audio has " + channels + " channels, a sample rate of " + samepleRate + " and bitdepth of " + bitDepth + ".");


short[] shorts = data.Select(b => (short)b).ToArray();

int size = 4096;
int window = 44100 * 10;
int y = 0;
Complex[] complexData = new Complex[size];
for (int i = window; i < window + size; i++) 
{
    Complex tmp = new Complex(shorts[i]/(double)short.MaxValue, 0);

    complexData[y] = tmp;
    y++;

}




FourierTransform.FFT(complexData, FourierTransform.Direction.Forward);


double[] arr = new double[complexData.Length];
//print out sample of conversion
for (int i = 0; i < complexData.Length; i++)
{
    arr[i] = complexData[i].Magnitude;

}

Console.Write("complete, ");


return arr; 

edit : changed to FFT fro DFT

1
Well you seem to be doing a DFT (which is even more precise than FFT) but how the returned data is structured, i don't know. Should be in the documentation of the library you are using. Fundamentally you'd be right if the data is structured lineary, but it could also be structured logarithmicly.MrPaulch
Thanks for pointing that out, I did mean to run fft, Just copied the code over when I was playing with the DFT.Pete B
You're basically on the right track - your bins are around 10 Hz wide as you calculated - see this answer for a fuller explanation.Paul R
There is no difference between FFT and DFT as far as accuracy is concerned - the FFT is just a much more efficient implementation of the DFT, but mathematically they are equivalent.Paul R
Sorry, most FFT algorithms are exactly as precise as DFT. To defend where i'm coming from: There are some FFT algorithms that trade off even more temporal efficiency for some accuracy.MrPaulch

1 Answers

1
votes

Here's a modified version of your code. Note the comments starting with "***".

OpenFileDialog file = new OpenFileDialog();
file.ShowDialog();
WaveFileReader reader = new WaveFileReader(file.FileName);

byte[] data = new byte[reader.Length];
reader.Read(data, 0, data.Length);

samepleRate = reader.WaveFormat.SampleRate;
bitDepth = reader.WaveFormat.BitsPerSample;
channels = reader.WaveFormat.Channels;

Console.WriteLine("audio has " + channels + " channels, a sample rate of " + samepleRate + " and bitdepth of " + bitDepth + ".");

// *** NAudio "thinks" in floats
float[] floats = new float[data.Length / sizeof(float)]
Buffer.BlockCopy(data, 0, floats, 0, data.Length);

int size = 4096;
// *** You don't have to fill the FFT buffer to get valid results.  More noisy & smaller "magnitudes", but better freq. res.
int inputSamples = samepleRate / 100; // 10ms... adjust as needed
int offset = samepleRate * 10 * channels;
int y = 0;
Complex[] complexData = new Complex[size];
// *** get a "scaling" curve to make both ends of sample region 0 but still allow full amplitude in the middle of the region.
float[] window = CalcWindowFunction(inputSamples);
for (int i = 0; i < inputSamples; i++)
{
    // *** "floats" is stored as LRLRLR interleaved data for stereo audio
    complexData[y] = new Complex(floats[i * channels + offset] * window[i], 0);
    y++;
}
// make sure the back portion of the buffer is set to all 0's
while (y < size)
{
    complexData[y] = new Complex(0, 0);
    y++;
}


// *** Consider using a DCT here instead...  It returns less "noisy" results
FourierTransform.FFT(complexData, FourierTransform.Direction.Forward);


double[] arr = new double[complexData.Length];
//print out sample of conversion
for (int i = 0; i < complexData.Length; i++)
{
    // *** I assume we don't care about phase???
    arr[i] = complexData[i].Magnitude;
}

Console.Write("complete, ");


return arr;

Once you get the results, and assuming a 44100 Hz sample rate and size = 4096, elements 2 - 4 should be the values you are looking for. There's a way to convert them to dB, but I don't remember it offhand.

Good luck!