Detecting piano note using C# with NAudio

1.3k views Asked by At

I am trying to write a program that identifies the note I play on a piano,I found out that the Goertzel filter is a easy to implement algorithm but I don't know how to use it.

Here is the code:

using NAudio.Wave;
using System.Windows;
using System;
using System.Collections.Generic;

namespace WpfTest {
    public partial class MainWindow : Window {
        private BufferedWaveProvider buffer;
        private WaveIn waveIn;
        private WaveOut waveOut;
        private const double TargetFreaquency = 261.626;//C4 note
        private const int SampleRate = 44100;

        public MainWindow() {
            InitializeComponent();
            InitializeSound();
            waveIn.StartRecording();
            waveOut.Play();
        }

        private void InitializeSound() {
            waveIn = new WaveIn();
            waveOut = new WaveOut();
            buffer = new BufferedWaveProvider(waveIn.WaveFormat);
            waveIn.DataAvailable += WaveInDataAvailable;
            waveOut.Init(buffer);
        }

        private void WaveInDataAvailable(object sender, WaveInEventArgs e) {
            buffer.AddSamples(e.Buffer, 0, e.BytesRecorded);

            var floatBuffer = new List<float>();
            for (int index = 0; index < e.BytesRecorded; index += 2) {
                short sample = (short)((e.Buffer[index + 1] << 8) |
                                        e.Buffer[index + 0]);
                float sample32 = sample / 32768f;
                floatBuffer.Add(sample32);
            }

            if (NotePlayed(floatBuffer.ToArray(), e.BytesRecorded)) {
                Console.WriteLine("You have played C4");
            }
        }

        private bool NotePlayed(float[] buffer, int end) {
            double power = GoertzelFilter(buffer, TargetFreaquency, buffer.Length);
            if (power > 500) return true;
            return false;
        }

        private double GoertzelFilter(float[] samples, double targetFreaquency, int end) {
            double sPrev = 0.0;
            double sPrev2 = 0.0;
            int i;
            double normalizedfreq = targetFreaquency / SampleRate;
            double coeff = 2 * Math.Cos(2 * Math.PI * normalizedfreq);
            for (i = 0; i < end; i++) {
                double s = samples[i] + coeff * sPrev - sPrev2;
                sPrev2 = sPrev;
                sPrev = s;
            }
            double power = sPrev2 * sPrev2 + sPrev * sPrev - coeff * sPrev * sPrev2;
            return power;
        }
    }
}

The code is not working correctly but how should I do to write in the console:"You have played C4" every time I play the C4 note to the microphone?

1

There are 1 answers

2
Bob C On BEST ANSWER

It looks like you're assuming that the microphone input will be 16-bit PCM samples at 44100Hz. That's not necessarily the case. You can check the 'default' microphone format, as well as force it to what you're expecting, as follows:

private void InitializeSound()
{
    waveIn = new WaveIn();

    // Add this here to see what the waveIn default format is.
    // Step through this line in the debugger.  If this isn't
    // 44100Hz sampling rate, 16-bit PCM, 1-channel, then that's
    // probably what's going wrong.
    WaveFormat checkformat = waveIn.WaveFormat;

    // Note that these are the default values if we used the 
    // parameterless WaveFormat constructor, but just expanding
    // here to show that we're forcing the input to what you're 
    // expecting:
    WaveFormat myformat = new WaveFormat(44100, 16, 2);
    waveIn.WaveFormat = myformat;
    SampleRate = myformat.SampleRate;

    waveIn.DataAvailable += WaveInDataAvailable;

    waveOut = new WaveOut();
    buffer = new BufferedWaveProvider(waveIn.WaveFormat);
    waveOut.Init(buffer);
}

I'm not sure on the endianness when you're converting the short to float in your event handler (I'm old, and I can't remember which end is which anymore :)), so that might also be an issue. You would probably be better off using BitConverter.ToInt16 to do that, rather than the shift/add that you're doing now:

private void WaveInDataAvailable(object sender, WaveInEventArgs e)
{
    buffer.AddSamples(e.Buffer, 0, e.BytesRecorded);

    var floatBuffer = new List<float>();
    for (int index = 0; index < e.BytesRecorded; index += 2)
    {
        short sample = BitConvert.ToInt16(e.Buffer, index);
        float sample32 = (float)sample;
        sample32 /= (float)Int16.MaxValue;
        floatBuffer.Add(sample32);
    }

    if (NotePlayed(floatBuffer.ToArray(), e.BytesRecorded))
    {
        Console.WriteLine("You have played C4");
    }
}

It also looks like the end parameter to NotePlayed is unused, which is actually good! Inferring the meaning of an end parameter, e.BytesRecorded wouldn't be the correct value anyway since that's not the sample count.