Audio 注意:开始检测

Audio 注意:开始检测,audio,signal-processing,onset-detection,Audio,Signal Processing,Onset Detection,我正在开发一个系统来帮助音乐家进行转录。其目的是在单乐器单声道录音上执行自动音乐转录(它不必是完美的,因为用户稍后会纠正故障/错误)。这里有人有自动音乐转录的经验吗?还是一般的数字信号处理?无论你的背景如何,任何人的帮助都是非常感激的 到目前为止,我已经研究了快速傅里叶变换在基音检测中的应用,在MATLAB和我自己的Java测试程序中的大量测试表明,它足够快速和准确,可以满足我的需要。任务中需要解决的另一个元素是以乐谱形式显示生成的MIDI数据,但这是我现在不关心的事情 简言之,我要寻找的是一种

我正在开发一个系统来帮助音乐家进行转录。其目的是在单乐器单声道录音上执行自动音乐转录(它不必是完美的,因为用户稍后会纠正故障/错误)。这里有人有自动音乐转录的经验吗?还是一般的数字信号处理?无论你的背景如何,任何人的帮助都是非常感激的

到目前为止,我已经研究了快速傅里叶变换在基音检测中的应用,在MATLAB和我自己的Java测试程序中的大量测试表明,它足够快速和准确,可以满足我的需要。任务中需要解决的另一个元素是以乐谱形式显示生成的MIDI数据,但这是我现在不关心的事情


简言之,我要寻找的是一种很好的音符开始检测方法,即信号中新音符开始的位置。由于慢速启动可能很难正确检测,我将首先使用钢琴录音系统。这部分也是因为我会弹钢琴,并且应该能够更好地获得适合测试的录音。如上所述,该系统的早期版本将用于简单的单声道录音,根据未来几周的进展情况,可能会在以后进行更复杂的输入。

您想要做的通常被称为WAV到MIDI(谷歌“WAV到MIDI”)。在这个过程中有很多尝试,但结果各不相同(注意,开始是困难之一;复调更难处理)。我建议从彻底搜索现成的解决方案开始,只有在没有可接受的解决方案的情况下才开始自己的工作

过程的另一部分是将MIDI输出作为传统乐谱进行渲染,但是有无数的产品可以做到这一点


另一个答案是:是的,我做了很多数字信号处理(见我网站上的软件-这是一个用VB和C编写的无限语音软件合成器),我有兴趣帮助您解决这个问题。WAV到MIDI部分在概念上并没有那么困难,只是让它在实践中可靠地工作,这很难。注意开始只是设置一个阈值-错误可以很容易地在时间上向前或向后调整,以补偿注意攻击的差异。音高检测在录音中比在实时中要容易得多,而且只需要实现一个自相关例程。

您想要做的通常被称为WAV到MIDI(谷歌“WAV到MIDI”)。在这个过程中有很多尝试,但结果各不相同(注意,开始是困难之一;复调更难处理)。我建议从彻底搜索现成的解决方案开始,只有在没有可接受的解决方案的情况下才开始自己的工作

过程的另一部分是将MIDI输出作为传统乐谱进行渲染,但是有无数的产品可以做到这一点


另一个答案是:是的,我做了很多数字信号处理(见我网站上的软件-这是一个用VB和C编写的无限语音软件合成器),我有兴趣帮助您解决这个问题。WAV到MIDI部分在概念上并没有那么困难,只是让它在实践中可靠地工作,这很难。注意开始只是设置一个阈值-错误可以很容易地在时间上向前或向后调整,以补偿注意攻击的差异。在录音中进行基音检测要比实时检测容易得多,而且只需要执行一个自相关例程。

以下是一个图表,说明了记录开始检测的阈值方法:

此图显示了一个典型的WAV文件,其中连续播放了三个离散音符。红线表示选择的信号阈值,蓝线表示由简单算法返回的注释开始位置,该算法在信号电平超过阈值时标记开始

short threshold = 10000;
int window_length = 100;
int running_total = 0;
// tally up the first window_length samples
for (int i = 0; i < window_length; i++)
{
    running_total += samples[i];
}
// calculate moving average
for (int i = window_length; i < samples.Length; i++)
{
    // remove oldest sample and add current
    running_total -= samples[i - window_length];
    running_total += samples[i];
    short moving_average = running_total / window_length;
    if (moving_average > threshold)
    {
        // here is one note onset point 
        int onset_point = i - (window_length / 2);
    }
}
如图所示,选择合适的绝对阈值很困难。在这种情况下,第一个音符拾得很好,第二个音符完全丢失,第三个音符(勉强)开始得很晚。通常,较低的阈值会使您拾取幻象笔记,而提高阈值会使您错过笔记。这个问题的一个解决方案是使用一个相对阈值,如果信号在一定时间内增加一定百分比,则触发启动,但这本身就有问题

一个更简单的解决方案是首先在wave文件上使用有点违反直觉的压缩(而不是MP3压缩-这完全是另一回事)。压缩基本上会使音频数据中的峰值变平,然后放大所有内容,使更多的音频接近最大值。对上述示例的影响如下(这说明了为什么“压缩”这个名称似乎毫无意义——在音频设备上,它通常被标记为“响度”):

在压缩之后,绝对阈值方法将工作得更好(尽管很容易过度压缩并开始拾取虚构的音符开始,效果与降低阈值相同)。有很多wave编辑器可以很好地进行压缩,最好让它们来处理这项任务——在检测wave文件中的注释之前,您可能需要做大量的工作来“清理”wave文件

在编码术语中,加载到内存中的WAV文件本质上只是一个两字节整数数组,其中0表示无信号,32767和-32768表示峰值。在其最简单的形式中,阈值检测算法只需从第一个样本开始,然后读取a
short threshold = 10000;
int window_length = 100;
int running_total = 0;
// tally up the first window_length samples
for (int i = 0; i < window_length; i++)
{
    running_total += samples[i];
}
// calculate moving average
for (int i = window_length; i < samples.Length; i++)
{
    // remove oldest sample and add current
    running_total -= samples[i - window_length];
    running_total += samples[i];
    short moving_average = running_total / window_length;
    if (moving_average > threshold)
    {
        // here is one note onset point 
        int onset_point = i - (window_length / 2);
    }
}
public void StaticCompress(short[] samples, float param)
{
    for (int i = 0; i < samples.Length; i++)
    {
        int sign = (samples[i] < 0) ? -1 : 1;
        float norm = ABS(samples[i] / 32768); // NOT short.MaxValue
        norm = 1.0 - POW(1.0 - norm, param);
        samples[i] = 32768 * norm * sign;
    }
}
public void StaticCompress(short[] samples, double param)
{
    for (int i = 0; i < samples.Length; i++)
    {
        Compress(ref samples[i], param);
    }
}

public void Compress(ref short orig, double param)
{
    double sign = 1;
    if (orig < 0)
    {
        sign = -1;
    }
    // 32768 is max abs value of a short. best practice is to pre-
    // normalize data or use peak value in place of 32768
    double norm = Math.Abs((double)orig / 32768.0);
    norm = 1.0 - Math.Pow(1.0 - norm, param);
    orig = (short)(32768.0 * norm * sign); // should round before cast,
        // but won't affect note onset detection
}