我一直在寻找不同的方法来检测传到麦克风中的音调。
看到我想找出它与特定音高的共振有多紧密,我想知道我是否可以做某种基于物理的共振算法。
如果你在钢琴上按住踏板,然后唱一个音调,(如果你离它现有的音高足够近)一个音符会产生共鸣。
我希望能够模拟这种行为。但是我将如何完成这项任务?谁能帮我推进这件事?
看一下自相关函数。
我发现一个有趣的解决方案是简单地将麦克风输入输入到 Karplus Strong 算法中。
所以 Karplus Strong 通过以下方式模拟弹拨弦:
现在如果我们将麦克风流添加到这个过程中,那么:
x = ( ringBuf[prev] + ring theBuf[prev2] ) * 0.5 * 0.998;
micOut[moPtr++] = x;
ringBuf[curr] = x + micIn[miPtr++];
它实际上非常准确地模拟了用吉他唱歌。如果你得到你的音调,它真的会哭泣。
But there is a severe problem with this approach: consider the pitch generated by a buffer of 100 elements, and that generated by a buffer of 101 elements. there is no way to generate any pitch in between these two values. we are limited to a discrete working set Of pitches. while this is going to be pretty accurate for low notes (A2 would have a buffer length of ~400), the higher we go the more the error: A7 would have a buffer length of ~12.5. That error is probably over a semitone.
I cannot see any way of countering this problem. I think the approach has to be dropped.
完全基于离散傅立叶变换 (DFT) 的算法具有许多缺点。一个问题是时间分辨率,因为 DFT 对窗口内的样本起作用,您无法确定该窗口内的音高变化。另一个问题是 DFT 的离散对数频率分辨率对于音调检测器可能不够好。毕竟,DFT 只能找到窗口大小为整数波长的波。
一个稍微高级的算法可以做这样的事情:
通过计算样本数量,您可以获得与样本频率匹配的音高分辨率。如果您想要比采样频率更高的分辨率,您可以将函数(例如多项式)拟合到峰值点周围的样本。既然你已经抑制了其他频率,你应该能够做到这一点。
正如另一个答案所暗示的那样,您还可以使用自相关来查找信号中的最大信号重复。但是我应该说,实现一个好的自相关音高检测器并非易事。在不知道的情况下,我会假设吉他调音器和类似的廉价电子产品将他们的算法基于带滤波器并结合计算峰值之间的样本距离。
您可以使用以输入为驱动力的阻尼谐振子。选择振荡器的参数,使其谐振频率与您想要的频率相匹配。
您会在大多数关于力学的理论物理书籍中找到对阻尼谐振子的分析。
我发现一种有用的方法是生成两个相隔 90 度的参考波(我称它们为“正弦”和“余弦”),然后在相当短的时间内将输入波形与这些参考波的点积(比如 1 /60 秒)输入的延伸。这将为您提供一个有点嘈杂的指标,表明您有多少输入频率与您的参考波同相或异相(使用两个参考波生成的值的平方和的平方根将是幅度)。如果窗口尺寸较小,您会注意到输出相当嘈杂,但如果您使用简单的 FIR 或 IIR 滤波器对输出进行过滤,您可能会得到相当合理的结果。
一个很好的技巧是生成两个幅度数:对于第一个,通过两轮滤波运行正弦和余弦幅度,然后计算平方和。第二,通过一轮滤波运行幅度,然后计算平方和,然后通过另一轮滤波运行。
两个幅度测量都会经历相同的延迟,但第一个比第二个更具选择性;因此,您可以非常清楚地判断频率是“正确开启”还是有点偏离。使用这种方法,可以快速检测 DTMF 音调,同时拒绝甚至偏离几赫兹的音调(偏离音调的音调在“松散”检测器上比在紧密检测器上拾取的音调强得多)。
示例代码:
双正弦相位,正弦频率; void process_some_waves(double *input, int16 len, 双*正弦相位,双正弦频率, 双 *sine_result, 双 *cosine_result) { 诠释我; 双相,sin_tot,cos_tot; 相位 = *正弦相位; sin_tot = cos_tot = 0; 对于 (i=0; len > i; i++) { sin_tot += 输入[i] * sin(相位); cos_tot += 输入[i] * cos(相位); 相位 += 正弦频率; } *sine_result = sin_tot; *cosine_result = cos_tot; *sine_phase = 相位; } /* 获取缓冲区中的第一个元素,并使用简单的高斯 resp 将其“涂抹”通过缓冲区。*/ void simple_fir_filter(double *buff, int buffsize) { 诠释我; 对于 (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 随便 双 sine_buff1[FILTER_SIZE1], sine_buff2[FILTER_SIZE2]; 双 cos_buff1[FILTER_SIZE1], cos_buff2[FILTER_SIZE2]; 双组合_buff[FILTER_SIZE2]; 双紧振幅,松振幅; 双参考相位; 无效句柄_some_data(双*输入) { /* 将结果放入过滤器缓冲区的第一个元素 */ process_some_waves(输入, SECTION_LENGTH, &ref_phase, FREQ, sine_buff1, cos_buff1); /* 运行第一阶段过滤 */ simple_fir_filter(sine_buff1, FILTER_SIZE1); simple_fir_filter(cosine_buff1, FILTER_SIZE1); /* 每个数组的最后一个元素将保存过滤结果。*/ /* 现在做第二阶段 */ sine_buff2[0] = sine_buff1[FILTER_SIZE1-1]; cosine_buff2[0] = cosine_buff1[FILTER_SIZE1-1]; 组合_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); 紧振幅 = sine_buff2[FILTER_SIZE2-1]*sine_buff2[FILTER_SIZE2-1] + cosine_buff2[FILTER_SIZE2-1]*cosine_buff2[FILTER_SIZE2-1]; 松散幅度=组合_buff2 [过滤器尺寸2-1]; }
此处的代码对除数组下标之外的所有数学运算都使用“双精度”。在实践中,用整数数学代替一些数学几乎肯定会更快。在具有浮点的机器上,我希望最好的方法是将相位保持为 32 位整数并使用约 4096 个“单”正弦值的表(RAM 中的表大小越小,缓存一致性越好表现)。我在定点(整数)DSP上使用了非常类似于上面的代码,并取得了巨大的成功;process_some_waves 中的正弦和余弦计算是在单独的“循环”中完成的,每个“循环”都被实现为带有“重复”前缀的单个指令。
我一直在阅读傅立叶分析。
基本上,如果您想从信号中提取频率 f,您只需输入正弦波频率 f,将其与原始信号相乘,然后积分
如果原始信号不包含任何频率为 f 的信号,则应该几乎为零。如果确实如此,那么您将测量该频率下信号中的能量。
尽管它背后有一些相当棘手的数学运算,但直观地说是有道理的:只要看看它,信号中处于频率 f 的所有东西都会对正弦波产生建设性干扰,留下残留物;不在频率 f 的所有事物本质上都可以被视为随机噪声,(即,在与我们的正弦波相乘时,零以上的东西几乎相同数量)没有净效应。一切都取消了。
这与我钓鱼的目的有关。为了完成我上面的类比:要检查钢琴包含哪些音符,您只需按住踏板并在其中唱一个上升的音调,每当发生共鸣共鸣时,您就可以记下钢琴在该频率上有一个音符。
当然,这也不是没有缺点:如果你按住 C1(这次没有踏板)并唱/弹 C2,C1 将以两倍的基本频率共振,产生 C2 的声音。
同样,播放 G2 会使其以三倍于其基频等共振