我一直在研究检测麦克风中唱歌音调的不同方法。
看到我想知道它与特定音级有多接近,我想知道我是否可以做一些基于物理的共振算法。
如果你按住钢琴上的延音踏板,并在其中唱出一个音调,(如果你足够接近它现有的一个音高),音符会同情地产生共鸣。
我很想能够模仿这种行为。但是我该怎么做呢?任何人都可以帮我推动这项工作吗?
答案 0 :(得分:2)
答案 1 :(得分:2)
我发现一个有趣的解决方案就是将麦克风输入转换为Karplus Strong算法。
所以Karplus Strong通过以下方式模拟弹拨的字符串:
现在,如果我们将麦克风流添加到此过程中,那么:
x = ( ringBuf[prev] + ring theBuf[prev2] ) * 0.5 * 0.998;
micOut[moPtr++] = x;
ringBuf[curr] = x + micIn[miPtr++];
它实际上模拟了真正准确地演唱吉他。如果你得到了你的语气点,那真的很难过。
但这种方法存在严重问题:考虑由100个元素的缓冲区生成的音高,以及由101个元素的缓冲区生成的音高。没有办法在这两个值之间产生任何间距。我们仅限于一组离散的工作。虽然这对于低音符(A2将具有~400的缓冲区长度)将是非常准确的,但是我们越高,误差越大:A7将具有~12.5的缓冲长度。这个错误可能超过了半音。
我看不出有任何方法可以解决这个问题。我认为必须放弃这种方法。
答案 2 :(得分:1)
完全基于离散傅立叶变换(DFT)的算法具有许多缺点。 一个问题是时间分辨率,因为DFT对窗口内的样本起作用,您无法确定该窗口内的音高变化。 另一个问题是DFT的离散对数频率分辨率,这对于音调检测器来说可能不够好。毕竟DFT只能找到窗口大小为整数波长的波。
稍微高级的算法可以做这样的事情:
通过计算样本数量,您可以获得与采样频率匹配的音高分辨率。 如果您想要比采样频率更高的分辨率,则可以将函数(例如多项式)拟合到峰值点周围的样本。由于你已经抑制了其他频率,你应该能够做到这一点。
如另一个答案所示,您还可以使用自相关来查找信号中的最大信号重复。但是我应该说实现一个好的自相关音调检测器并非易事。在不知情的情况下,我会假设吉他调谐器和类似的便宜电子设备将他们的算法基于带滤波器并结合计算峰值之间的样本距离。
答案 3 :(得分:1)
您可以使用带有输入作为驱动力的阻尼谐振子。选择振荡器的参数,使其谐振频率与您想要的频率相匹配。
你会在大多数关于力学的理论物理书籍中找到对阻尼谐振子的分析。
答案 4 :(得分:1)
我发现一种有用的方法是产生两个相隔90度的参考波(我称之为“正弦”和“余弦”)并将输入波形的点积与这些参考波相比较短(比如1/60秒)延伸输入。这将为您提供一个有点噪声的指示器,指示您有多少输入频率与参考波同相或异相(使用两个参考波生成的值的平方和的平方根将是振幅)。对于小窗口大小,您会注意到输出相当嘈杂,但如果您使用简单的FIR或IIR滤波器来过滤输出,您可能应该得到一些非常合理的东西。
一个很好的技巧是产生两个幅度数:对于第一个,通过两轮滤波运行正弦和余弦幅度,然后计算平方和。对于第二个,通过一轮过滤运行幅度,然后计算平方的总和,然后通过另一轮过滤运行它。
两个幅度测量都将经历相同的延迟,但第一个将比第二个更具选择性;因此,您可以非常清楚地说明频率是“正确开启”还是略微偏离。使用这种方法,可以快速检测到DTMF音调,同时拒绝甚至几Hz关闭的音调(非松散的音调将在“松散”探测器上比紧密音调更强烈地拾取。)
示例代码:
double sine_phase,sine_freq; void process_some_waves(double *input, int16 len, double *sine_phase, double sine_freq, double *sine_result, double *cosine_result) { int i; double phase, sin_tot,cos_tot; phase = *sine_phase; sin_tot = cos_tot = 0; for (i=0; len > i; i++) { sin_tot += input[i] * sin(phase); cos_tot += input[i] * cos(phase); phase += sine_freq; } *sine_result = sin_tot; *cosine_result = cos_tot; *sine_phase = phase; } /* Takes first element in buffer and 'smears' it through buffer with simple Gaussian resp. */ void simple_fir_filter(double *buff, int buffsize) { int i; for (i=buffsize-1; i>=2; i--) buff[i] = (buff[i-1] + buff[i-2])/2; } #define FILTER_SIZE1 10 #define FILTER_SIZE2 8 #define SECTION_LENGTH 128 #define FREQ whatever double sine_buff1[FILTER_SIZE1], sine_buff2[FILTER_SIZE2]; double cos_buff1[FILTER_SIZE1], cos_buff2[FILTER_SIZE2]; double combined_buff[FILTER_SIZE2]; double tight_amplitude, loose_amplitude; double ref_phase; void handle_some_data(double *input) { /* Put results in first element of filter buffers */ process_some_waves(input, SECTION_LENGTH, &ref_phase, FREQ, sine_buff1, cos_buff1); /* Run first stage of filtering */ simple_fir_filter(sine_buff1, FILTER_SIZE1); simple_fir_filter(cosine_buff1, FILTER_SIZE1); /* Last element of each array will hold results of filtering. */ /* Now do second stage */ sine_buff2[0] = sine_buff1[FILTER_SIZE1-1]; cosine_buff2[0] = cosine_buff1[FILTER_SIZE1-1]; combined_buff[0] = sine_buff2[0]*sine_buff2[0] + cosine_buff2[0]*cosine_buff2[0]; simple_fir_filter(sine_buff2, FILTER_SIZE2); simple_fir_filter(cosine_buff2, FILTER_SIZE2); simple_fir_filter(combined_buff, FILTER_SIZE2); tight_amplitude = sine_buff2[FILTER_SIZE2-1]*sine_buff2[FILTER_SIZE2-1] + cosine_buff2[FILTER_SIZE2-1]*cosine_buff2[FILTER_SIZE2-1]; loose_amplitude = combined_buff2[FILTER_SIZE2-1]; }
此处的代码使用“double”表示除数组下标之外的所有数学。在实践中,用整数数学替换一些数学几乎肯定会更快。在具有浮点的机器上,我希望最好的方法是将相位保持为32位整数并使用~4096'单个正弦值表(RAM中的表大小越小,缓存一致性越好)性能)。我在定点(整数)DSP上使用了非常类似的代码并取得了巨大的成功; process_some_waves中的正弦和余弦计算是在单独的“循环”中完成的,每个“循环”都被实现为带有“重复”前缀的单个指令。
答案 5 :(得分:0)
我一直在阅读傅立叶分析。
基本上如果你想从信号中提取频率f,你只需要输入正弦波频率f,将它与原始信号相乘,然后进行积分
如果原始信号不包含任何频率f,那么你应该得到几乎为零。如果它有,那么你将得出信号在该频率下的能量的量度。
虽然背后有一些相当棘手的数学,但它直观地说是有道理的:只要看一下,频率为f的信号中的所有内容都会与正弦波产生干扰;不是频率f的所有东西都可以基本上被认为是随机噪声(即,几乎有相同数量的东西,如下所示),当乘以我们的正弦波时没有净效应。一切都取消了。
这与我钓鱼的内容有关。完成上面的类比:要检查钢琴包含哪些音符,只需按住踏板并向其中唱出一个上升的音调,每当发生交感神经共振时,您可以记下钢琴在该频率处有音符。
当然,这并非没有失败:如果你按住C1(这次没有踏板)并唱/播C2,C1会以两倍的基频共鸣,产生C2声音。
同样地玩G2会使其在基频等的三倍时产生共鸣