我正在开发一个系统来帮助音乐家进行转录.目的是在单个乐器单声道录音上执行自动音乐转录(它不一定是完美的,因为用户将在以后纠正错误/错误).这里有没有人有自动音乐转录的经验?或一般的数字信号处理?无论您的背景如何,都非常感谢任何人的帮助.
到目前为止,我已经研究了使用快速傅里叶变换进行音调检测,并且MATLAB和我自己的Java测试程序中的大量测试表明它足够快速和准确以满足我的需求.需要解决的任务的另一个要素是以乐谱形式显示制作的MIDI数据,但这是我现在不关心的事情.
简而言之,我正在寻找的是一种用于音符开始检测的好方法,即信号中新音符开始的位置.由于慢速开启可能很难正确检测,我最初将使用带有钢琴录音的系统.这也部分归因于我弹钢琴的事实,应该处于更好的位置以获得合适的录音进行测试.如上所述,该系统的早期版本将用于简单的单声道录音,根据未来几周取得的进展,可能会在稍后进行更复杂的输入.
这是一个图形,说明了记录开始检测的阈值方法:
此图显示了一个典型的WAV文件,其中连续播放了三个不连续的音符.红线表示选择的信号阈值,蓝线表示由简单算法返回的音符开始位置,该算法在信号电平超过阈值时标记开始.
如图所示,选择合适的绝对阈值很困难.在这种情况下,第一个音符被正确拾取,第二个音符被完全错过,第三个音符(几乎没有)开始很晚.一般来说,低阈值会导致您获取幻像音符,而提高它会导致您错过音符.该问题的一个解决方案是使用相对阈值,如果信号在一定时间内增加一定百分比,则该相对阈值触发启动,但这具有其自身的问题.
一个更简单的解决方案是首先在波形文件上使用有点违反直觉命名的压缩(不是MP3压缩 - 这完全是其他东西).压缩基本上会使音频数据中的峰值变平,然后放大所有内容,以使更多音频接近最大值.对上述样本的影响看起来像这样(这说明为什么名称"压缩"似乎毫无意义 - 在音频设备上它通常被标记为"响度"):
在压缩之后,绝对阈值方法将更好地工作(尽管过度压缩和开始拾取虚构音符开始很容易,与降低阈值的效果相同).有很多波形编辑器可以很好地完成压缩,最好让他们处理这个任务 - 你可能需要做大量的工作"清理"你的波形文件才能检测到音符无论如何他们.
在编码术语中,加载到存储器中的WAV文件基本上只是一个双字节整数的数组,其中0表示无信号,32,767和-32,768表示峰值.在其最简单的形式中,阈值检测算法将从第一个样本开始并读取数组,直到找到大于阈值的值.
short threshold = 10000; for (int i = 0; i < samples.Length; i++) { if ((short)Math.Abs(samples[i]) > threshold) { // here is one note onset point } }
在实践中,这非常有效,因为正常音频具有高于给定阈值的各种瞬态尖峰.一种解决方案是使用运行平均信号强度(即,在最后n个样本的平均值高于阈值之前不标记开始).
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); } }
所有这些都需要进行大量调整并使用设置来使其准确地找到WAV文件的起始位置,并且通常对一个文件有效的方法在另一个文件上不能很好地工作.这是你选择的一个非常困难且不完美解决的问题领域,但我认为你正在解决这个问题很酷.
更新:此图显示了我遗漏的音符检测的详细信息,即检测音符何时结束:
黄线表示关闭阈值.一旦算法检测到音符开始,它就会假定音符继续,直到运行平均信号强度低于此值(此处用紫色线表示).当然,这是另一个困难的来源,就像两个或多个音符重叠(复音)的情况一样.
一旦检测到每个音符的起点和终点,您现在可以分析WAV文件数据的每个片段以确定音高.
更新2:我刚看了你更新的问题.如果你是从头开始编写自己的,那么通过自相关进行间距检测比FFT更容易实现,但是如果你已经检出并使用了预先构建的FFT库,那么你最好还是使用它.一旦你确定了每个音符的开始和停止位置(并且在开始和结束时包括一些填充以用于错过的攻击和释放部分),你现在可以拉出每个音频数据片段并将其传递给FFT函数确定音高.
这里的一个重点是不使用压缩音频数据的片段,而是使用原始未修改数据的片段.压缩过程会扭曲音频并可能产生不准确的音高读数.
关于音符攻击时间的最后一点是,它可能不像你想象的那么严重.通常在音乐中,具有慢速攻击的乐器(如软合成器)将比尖锐的攻击乐器(如钢琴)更早地开始音符,并且两个音符听起来好像它们同时开始.如果你正在以这种方式演奏乐器,那么算法可以为两种乐器提供相同的开始时间,从WAV到MIDI的角度来看这是很好的.
最后更新(我希望):忘掉我所说的关于在每个音符的早期攻击部分包含一些填充样本的内容 - 我忘了这对于音高检测实际上是个坏主意.许多乐器(特别是钢琴和其他打击乐器乐器)的攻击部分包含的瞬态不是基本音高的倍数,并且会使音调检测变得棘手.出于这个原因,你实际上想要在攻击后稍微开始每个切片.
哦,有点重要:这里的术语"压缩"并不是指MP3式压缩.
再次更新:这是一个非动态压缩的简单函数:
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; } }
当param = 1.0时,此功能对音频没有影响.较大的参数值(2.0是好的,它将使每个样本和最大峰值之间的归一化差异平方)将产生更多压缩和更大声(但是蹩脚)的声音.低于1.0的值将产生扩展效果.
另一个可能是显而易见的一点:你应该将音乐录制在一个小的,非回声的房间里,因为这个算法通常会以幻象音符的形式拾取回声.
更新:这是一个StaticCompress版本,它将在C#中编译,并且明确表达所有内容.这将返回预期结果:
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 }
对不起,我在Matlab上的知识得分是0.如果你发布了另一个问题,为什么你的Matlab函数没有按预期工作,它会得到答案(只是不是我).