Convert audio stream to frequency
Asked Answered
G

1

6

I've managed to successfully get a stream of audio data going to an output device (speaker) using NAudio:

private void OnDataAvailable(object sender, WaveInEventArgs e)
        {
            var buffer = e.Buffer;
            var bytesRecorded = e.BytesRecorded;
            Debug.WriteLine($"Bytes {bytesRecorded}");

And the sample output:

Bytes 19200
Bytes 19200
Bytes 19200
Bytes 19200
Bytes 19200
Bytes 19200
Bytes 19200
Bytes 19200
Bytes 19200
Bytes 19200
Bytes 19200
Bytes 23040
Bytes 19200
Bytes 19200
Bytes 19200
Bytes 19200
Bytes 19200

I then transform (FFT) this to x and y values using https://stackoverflow.com/a/20414331:

var buffer = e.Buffer;
            var bytesRecorded = e.BytesRecorded;
            //Debug.WriteLine($"Bytes {bytesRecorded}");
            var bufferIncrement = _waveIn.WaveFormat.BlockAlign;

            for (var index = 0; index < bytesRecorded; index += bufferIncrement)
            {
                var sample32 = BitConverter.ToSingle(buffer, index);
                _sampleAggregator.Add(sample32);
            }

With a sample output of:

x: -9.79634E-05, y: -9.212703E-05
x: 6.897306E-05, y: 2.489315E-05
x: 0.0002080683, y: 0.0004317867
x: -0.0001720883, y: -6.681971E-05
x: -0.0001245111, y: 0.0002880402
x: -0.0005751926, y: -0.0002682915
x: -5.280507E-06, y: 7.297558E-05
x: -0.0001143928, y: -0.0001156801
x: 0.0005231025, y: -0.000153206
x: 0.0001011164, y: 7.681748E-05
x: 0.000330695, y: 0.0002293986

Not sure if this is even possible or if I'm just misunderstanding what the stream is returning, but I'd like to get the frequency of the audio stream in order to do some stuff with Philips Hue. The x, y values above are way to small to use in the CIE colour space. Am I doing something wrong or am I completely misunderstanding what the data is in the buffer in OnDataAvailable?

Thanks!

Edit:

I've modified my OnDataAvailable code based on comments and the tutorial for the Autotune program to be the below:

private void OnDataAvailable(object sender, WaveInEventArgs e)
        {
            var buffer = e.Buffer;
            float sample32 = 0;

            for (var index = buffer.Length > 1024 ? buffer.Length - 1024 : buffer.Length; index < e.BytesRecorded; index += 2)
            {
                var sample = (short) ((buffer[index + 1] << 8) | buffer[index + 0]);
                sample32 = sample / 32768f;
                Debug.WriteLine(sample32);
                LightsController.SetLights(Convert.ToByte(Math.Abs(sample32) * 255));
                _sampleAggregator.Add(sample32);
            }
            var floats = BytesToFloats(buffer);

            if (sample32 != 0.0f)
            {
                var pitchDetect = new FftPitchDetector(sample32);
                var pitch = pitchDetect.DetectPitch(floats, floats.Length);
                Debug.WriteLine($"Pitch {pitch}");
            }
        }

The hope is that I only use the last set of elements from the buffer as it doesn't seem to clear itself and I'm only interested in the latest set of data available in order to get the frequency of the current audio. However, i still get an index exception ocassionally when the DetectPitch method is called. Where am I going wrong? I was hoping to use the frequency to change the colour and brightness of hue bulbs.

Glucoside answered 15/2, 2017 at 18:22 Comment(5)
Have you seen this post: #15009584 ?Saltcellar
@DavidTansey I have not. Will look into it and report back.Glucoside
@DavidTansey I modified my OnDataAvailable code to match and pass in "floats" and "floats.Length" to pitchDetect.DetectPitch but I get an index out of range exception at SmbPitchShift.smbFft(fftBuffer, frames, -1); and if commented out, at float real = fftBuffer[bin * 2];. Any ideas?Glucoside
Modifed to match what -- do you mean the code shown by the OP in that post or the code shown (via link to an article) in Mark Heath's answer there? Also if you look at the comments beneath Mark's answer you will see that someone had an index out of range problem and Mark suggested how to fix it in an additional comment.Saltcellar
@DavidTansey both, they're the same code. From Mark's answer, isn't the point of the SampleAggregator to make things a power of two, so I shouldn't have to change what I pass through to pitch detector?Glucoside
D
1

Use

fPeak = SamplingRate * BinNumberOfPeak / FFTLength ;

Defecate answered 25/2, 2017 at 8:47 Comment(2)
What's "BinNumberOfPeak"? And where's "FFTLength"? You're answer doesn't provide much detail. I'd like to understand what the data NAudio is returning represents as well as how to get the frequency(ies) from the data?Glucoside
FFT Length = 4096 (= N number of computation points) and Bin number is index in FFT array 0, 1, 2 .. Frequency peak computation is tricky based on one-sided or two-sided spectra. You must follow point 4 here for understanding authentic calculations.Defecate

© 2022 - 2024 — McMap. All rights reserved.