使用 C# 和 NAudio 检测钢琴音符
Detecting piano note using C# with NAudio
我正在尝试编写一个程序来识别我在钢琴上弹奏的音符,我发现 Goertzel 滤波器是一种易于实现的算法,但我不知道如何使用它。
代码如下:
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;
}
}
}
代码运行不正常但是我应该如何在控制台中写入:"You have played C4"每次我对着麦克风播放C4音符?
您似乎假设麦克风输入是 44100Hz 的 16 位 PCM 样本。不一定如此。您可以检查 'default' 麦克风格式,并将其强制为您所期望的格式,如下所示:
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);
}
当您在事件处理程序中将 short
转换为 float
时,我不确定字节顺序(我老了,我不记得哪一端是哪一端了再 :)),所以这也可能是一个问题。你可能最好使用 BitConverter.ToInt16
来做到这一点,而不是你现在正在做的 shift/add:
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");
}
}
看起来NotePlayed
的end
参数也没有用到,其实也不错!推断 end
参数的含义,e.BytesRecorded
无论如何都不是正确的值,因为那不是样本计数。
我正在尝试编写一个程序来识别我在钢琴上弹奏的音符,我发现 Goertzel 滤波器是一种易于实现的算法,但我不知道如何使用它。
代码如下:
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;
}
}
}
代码运行不正常但是我应该如何在控制台中写入:"You have played C4"每次我对着麦克风播放C4音符?
您似乎假设麦克风输入是 44100Hz 的 16 位 PCM 样本。不一定如此。您可以检查 'default' 麦克风格式,并将其强制为您所期望的格式,如下所示:
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);
}
当您在事件处理程序中将 short
转换为 float
时,我不确定字节顺序(我老了,我不记得哪一端是哪一端了再 :)),所以这也可能是一个问题。你可能最好使用 BitConverter.ToInt16
来做到这一点,而不是你现在正在做的 shift/add:
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");
}
}
看起来NotePlayed
的end
参数也没有用到,其实也不错!推断 end
参数的含义,e.BytesRecorded
无论如何都不是正确的值,因为那不是样本计数。